1

我有一个多态不可变类的层次结构,我想使用 Newtonsoft JSON 从 JSON 序列化和反序列化。

这是一组简化的示例类:

[JsonConverter(typeof(ItemJsonConveter))]
public abstract class Item
{
    [JsonConstructor]
    public Item(string itemType, IList<Item> subItems)
    {
        ItemType = itemType;
        SubItems = subItems ?? new List<Item>();
    }

    public string ItemType { get; }

    public IList<Item> SubItems { get; }
}


public class Foo : Item
{
    public const string TypeName = "foo";

    [JsonConstructor]
    public Foo(string fooProperty, IList<Item> subItems = null) : base(TypeName, subItems)
    {
        FooProperty = fooProperty;
    }

    public string FooProperty { get; }
}

public class Bar : Item
{
    public const string TypeName = "bar";

    public Bar(string barProperty, IList<Item> subItems = null) : base(TypeName, subItems)
    {
        BarProperty = barProperty;
    }

    public string BarProperty { get; }
}

我需要生成的 JSON 遵循特定格式,该格式不通过完整的类名或使用 $type 参数来识别类型,就像其他值一样TypeNameHandling

因此,我创建了以下ItemJsonConverter类来从 JSON 中读取我的自定义“ItemType”属性,以确定要反序列化到的正确类型。

internal class ItemJsonConveter : JsonConverter
{
    public override bool CanWrite => false;

    public override bool CanConvert(Type objectType)
    {
        if (typeof(Item).IsAssignableFrom(objectType)) return true;
        else return false;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        string typeValue = jo[nameof(Item.ItemType)].Value<string>();

        switch (typeValue)
        {
            case Foo.TypeName:
                return jo.ToObject<Foo>();

            case Bar.TypeName:
                return jo.ToObject<Bar>();

            default:
                throw new InvalidOperationException("Unxpected item type: " + typeValue);
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

这是创建的 JSON

{
  "FooProperty": "Foo 1",
  "ItemType": "foo",
  "SubItems": [
    {
      "BarProperty": "Bar 2",
      "ItemType": "bar",
      "SubItems": [
        {
          "FooProperty": "Foo 3",
          "ItemType": "foo",
          "SubItems": []
        },
        {
          "BarProperty": "Bar 3",
          "ItemType": "bar",
          "SubItems": []
        }
      ]
    },
    {
      "FooProperty": "Foo 2",
      "ItemType": "foo",
      "SubItems": []
    }
  ]
}

然而,当我运行以下代码来测试这个时,aStackOverFlowException被抛出ItemJsonConverter.ReadJson()

//Create Model
Foo foo3 = new Foo("Foo 3");
Bar bar3 = new Bar("Bar 3");
Foo foo2 = new Foo("Foo 2");
Bar bar2 = new Bar("Bar 2", new List<Item>() { foo3, bar3 });
Foo foo1 = new Foo("Foo 1", new List<Item>() { bar2, foo2 });

//Create serializer
var ser = new Newtonsoft.Json.JsonSerializer
{
    Formatting = Newtonsoft.Json.Formatting.Indented,
    TypeNameHandling = Newtonsoft.Json.TypeNameHandling.None,
};

//Serialize modelt to string
var tw = new StringWriter();
ser.Serialize(tw, foo1);
var stringvalue = tw.ToString();

//Deserialize
try
{
    using (var sr = new StringReader(stringvalue))
    {
        using (var jr = new JsonTextReader(sr))
        {
            //This throws StackOverflowException ItemJsonConveter.ReadJson()
            var deserialziedFoo = ser.Deserialize<Foo>(jr);
        }
    }
}
catch (System.Exception exp)
{
    throw;
}

我已经阅读了关于同一问题的其他类似问题,解决方案是将调用替换JObject.ToObject<T>()为新对象的显式构造,然后使用JsonConvert.PopulateObject<t>()JSON 中的值更新对象。

但是,我希望这些对象是完全不可变的,并且在构造后无法更改它们。(除非这是让它工作的唯一方法。)

我可以通过手动从JObjectin 中读取值并直接ReadJson()调用FooBar构造函数来使其工作。但是,对于更复杂的对象,我真的很想利用 Newtonsoft 通过属性和反射自动序列化/反序列化的能力,而不必在每次类更改时更新我的​​序列化代码。

这是作为 Gist 的完整示例代码: https ://gist.github.com/aireq/51e4527886ddb076ee8c981264b439a7

4

0 回答 0