我有一个多态不可变类的层次结构,我想使用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()
读取值并直接调用Foo
和Bar
构造函数来使其工作。但是,对于更复杂的对象,我真的很想利用Newtonsoft通过属性和反射自动进行序列化/反序列化的功能,而不必每次类更改时都更新我的序列化代码。
以下是要点的完整示例代码: https://gist.github.com/aireq/51e4527886ddb076ee8c981264b439a7