我有一个多态不可变类的层次结构,我想使用 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 中的值更新对象。
但是,我希望这些对象是完全不可变的,并且在构造后无法更改它们。(除非这是让它工作的唯一方法。)
我可以通过手动从JObject
in 中读取值并直接ReadJson()
调用Foo
和Bar
构造函数来使其工作。但是,对于更复杂的对象,我真的很想利用 Newtonsoft 通过属性和反射自动序列化/反序列化的能力,而不必在每次类更改时更新我的序列化代码。
这是作为 Gist 的完整示例代码: https ://gist.github.com/aireq/51e4527886ddb076ee8c981264b439a7