将具有基础对象数组的 class 反序列化为具有继承对象数组 (Newtonsoft.Json) 的 class

Deserialize class with an array of base objects into class with an array of inherited objects (Newtonsoft.Json)

我一直在开发一款音乐游戏,并决定添加其他游戏关卡的转换。我决定从使用 JSON 来存储它的关卡的游戏之一,所以我使用 Newtonsoft.Json 来反序列化关卡数据。级别可以有 2 种对象类型,它们存储在一个 array/list 中,其中一个共享 属性 和一个单独的 属性。牢记这一点,我用它的属性制作了级别 class,一个基础和两个继承的 classes:

class Chart
{
   //Some unimportant properties
   //...

   public Note[] note;

   class Note
   {
      public int[] beat;
   }

   class NoteSingle : Note
   {
      public int x;
   }

   class NoteRain : Note
   {
      public int[] endbeat;
   }
}

但是,当我尝试反序列化级别时,note 只包含基础对象。我尝试创建 JsonSerializerSettings 并将 TypeNameHandling 设置为 All 并将其传递给反序列化方法,但它没有用,note 仍然只有基础 classes在里面。

基本上我需要从文件加载级别,将其反序列化为 Chart 并使 note 中的每个注释成为从 Note 继承的类型之一,具体取决于 json数据。就像如果注释有 x 字段然后将其加载为 NoteSingle 并且如果它有 endbeat 字段则将其加载为 NoteRain.

代表

class Note
{
    public int[] beat;
}

class NoteSingle : Note
{
    public int x;
}

class NoteRain : Note
{
    public int[] endbeat;
}

class Chart
{
    //Some unimportant properties
    public Note[] note;
    //Some unimportant properties
}

public static void Convert(string path)
{
    string rawData = File.ReadAllText(path);
    JsonSerializerSettings setts = new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.All
    };
    Chart ch = JsonConvert.DeserializeObject<Chart>(rawData, setts);

    //Level converter code
}

我正在尝试加载的示例数据:https://pastebin.com/zgnRsgWZ

我做错了什么?

我尝试使用此代码:

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All
};

var chart = new Chart();
chart.note = new Note[]
{
        new NoteSingle { x = 37 },
        new NoteRain { endbeat = new[] { 9 } }
};

var json = JsonConvert.SerializeObject(chart, settings);
var chart2 = JsonConvert.DeserializeObject<Chart>(json, settings);

它正在运行。 json 具有此值:

{
   "$type":"Test.Chart, SoApp",
   "note":{
      "$type":"Test.Chart+Note[], SoApp",
      "$values":[
         {
            "$type":"Test.Chart+NoteSingle, SoApp",
            "x":37,
            "beat":null
         },
         {
            "$type":"Test.Chart+NoteRain, SoApp",
            "endbeat":{
               "$type":"System.Int32[], mscorlib",
               "$values":[
                  9
               ]
            },
            "beat":null
         }
      ]
   }
}

chart2NoteSingleNoteRain两种音符。也许您没有在 Serialize 中使用 TypeNameHandling.All。您需要在 SerializeDeserialize.

上同时使用

更新

如果您无法控制生成的 JSON,您可以使用转换器对其进行反序列化:

public class YourJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var chart = new Chart();
        var notes = new List<Note>();

        string name = null;
        NoteRain noteRain = null;

        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.PropertyName:
                    name = reader.Value.ToString();
                    break;
                    
                case JsonToken.Integer:
                    if (name == "x")
                    {
                        var note = new NoteSingle { x = Convert.ToInt32(reader.Value) };
                        notes.Add(note);
                    }
                    else if (name == "endbeat")
                    {
                        if (noteRain == null)
                        {
                            noteRain = new NoteRain { endbeat = new[] { Convert.ToInt32(reader.Value) } };
                            notes.Add(noteRain);
                        }
                        else
                        {
                            var array = noteRain.endbeat;
                            noteRain.endbeat = new int[noteRain.endbeat.Length + 1];
                                
                            for (int i = 0; i < array.Length; i++)
                            {
                                noteRain.endbeat[i] = array[i];
                            }

                            noteRain.endbeat[noteRain.endbeat.Length - 1] = Convert.ToInt32(reader.Value);
                        }
                    }

                    break;
            }
        }

        chart.note = notes.ToArray();
        return chart;
    }

    public override bool CanWrite => false;

    public override bool CanConvert(Type objectType)
    {
        return true;
    }
}

这是一个简单的例子,你必须调整它,但我认为它很容易做到。在 属性 名称中,我得到了 属性 的名称,稍后我将使用它来创建正确的类型。如果我处理一个 x 属性 我知道那是一个 NoteSingle 我创建它并添加到 notes 列表。 例如,如果您得到一个 属性 名称,如 beat,而您还不知道 Note class 的类型,请使用临时变量来存储和填充稍后,当你读到一个你知道来自具体 class 的 属性 时,创建 Note 实例并用这个变量填充。之后,如果您阅读了此 class 的更多数据,请继续填充您的实例。

用法:

var settings = new JsonSerializerSettings();
settings.Converters.Add(new YourJsonConverter());
var chart = JsonConvert.DeserializeObject<Chart>(json, settings);