使用Newtonsoft JSON反序列化不可变多态类型的层次结构时发生StackOverflowException

时间:2019-02-05 22:33:09

标签: c# json json.net stack-overflow jsonconverter

我有一个多态不可变类的层次结构,我想使用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": []
    }
  ]
}

但是,当我运行以下代码进行测试时,在StackOverFlowException中抛出了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。

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

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

以下是要点的完整示例代码: https://gist.github.com/aireq/51e4527886ddb076ee8c981264b439a7

0 个答案:

没有答案