Json.Net 具有多态子对象的类型序列化
Json.Net Serialization of Type with Polymorphic Child Object
我们希望能够 serialize/deserialize json from/to C# classes,主要 class 有一个多态子实例目的。使用 Json.Net 的 TypeNameHandling.Auto 设置很容易做到这一点。但是,我们希望在没有“$type”字段的情况下这样做。
第一个想法是能够将“$type”重命名为我们选择的值,并使该类型的值成为一个可以正确映射子类型的枚举。我还没有将其视为一种选择,但很高兴听到它是否可行。
第二个想法是沿着以下几行...下面是 classes 的第一遍,顶层 class 有一个关于数据类型的指标 (SubTypeType)包含在子对象 (SubTypeData) 中。我深入研究了 Json.Net 文档并尝试了一些方法,但没有成功。
我们目前可以完全控制数据定义,但一旦部署,事情就被锁定了。
public class MainClass
{
public SubType SubTypeType { get; set; }
public SubTypeClassBase SubTypeData { get; set; }
}
public class SubTypeClassBase
{
}
public class SubTypeClass1 : SubTypeClassBase
{
public string AaaField { get; set; }
}
public class SubTypeClass2 : SubTypeClassBase
{
public string ZzzField { get; set; }
}
在容器 class 中包含子类型信息是有问题的,原因有两个:
- 当 Json.NET 正在读取包含的 class 时无法访问容器 class 实例。
- 如果您以后需要将
SubTypeClassBase
属性 转换成一个列表,那么子类型信息将无处可放。
相反,我建议在基础 class:
中添加子类型信息作为 属性
[JsonConverter(typeof(SubTypeClassConverter))]
public class SubTypeClassBase
{
[JsonConverter(typeof(StringEnumConverter))] // Serialize enums by name rather than numerical value
public SubType Type { get { return typeToSubType[GetType()]; } }
}
现在,只要序列化可分配给 SubTypeClassBase
的对象,自定义子类型枚举就会被序列化。完成后,为了反序列化,您可以创建一个 JsonConverter
that loads the json for a given SubTypeClassBase
into a temporary JObject
,检查 "Type"
属性 的值,并将 JSON 对象反序列化为适当的 class。
下面的原型实现:
public enum SubType
{
BaseType,
Type1,
Type2,
}
[JsonConverter(typeof(SubTypeClassConverter))]
public class SubTypeClassBase
{
static readonly Dictionary<Type, SubType> typeToSubType;
static readonly Dictionary<SubType, Type> subTypeToType;
static SubTypeClassBase()
{
typeToSubType = new Dictionary<Type,SubType>()
{
{ typeof(SubTypeClassBase), SubType.BaseType },
{ typeof(SubTypeClass1), SubType.Type1 },
{ typeof(SubTypeClass2), SubType.Type2 },
};
subTypeToType = typeToSubType.ToDictionary(pair => pair.Value, pair => pair.Key);
}
public static Type GetType(SubType subType)
{
return subTypeToType[subType];
}
[JsonConverter(typeof(StringEnumConverter))] // Serialize enums by name rather than numerical value
public SubType Type { get { return typeToSubType[GetType()]; } }
}
public class SubTypeClass1 : SubTypeClassBase
{
public string AaaField { get; set; }
}
public class SubTypeClass2 : SubTypeClassBase
{
public string ZzzField { get; set; }
}
public class SubTypeClassConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(SubTypeClassBase);
}
public override bool CanWrite { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
var typeToken = token["Type"];
if (typeToken == null)
throw new InvalidOperationException("invalid object");
var actualType = SubTypeClassBase.GetType(typeToken.ToObject<SubType>(serializer));
if (existingValue == null || existingValue.GetType() != actualType)
{
var contract = serializer.ContractResolver.ResolveContract(actualType);
existingValue = contract.DefaultCreator();
}
using (var subReader = token.CreateReader())
{
// Using "populate" avoids infinite recursion.
serializer.Populate(subReader, existingValue);
}
return existingValue;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
您可以尝试 JsonSubtypes 支持使用枚举值注册类型映射的转换器实现。
在你的情况下它看起来像这样:
public class MainClass
{
public SubTypeClassBase SubTypeData { get; set; }
}
[JsonConverter(typeof(JsonSubtypes), "SubTypeType")]
[JsonSubtypes.KnownSubType(typeof(SubTypeClass1), SubType.WithAaaField)]
[JsonSubtypes.KnownSubType(typeof(SubTypeClass2), SubType.WithZzzField)]
public class SubTypeClassBase
{
public SubType SubTypeType { get; set; }
}
public class SubTypeClass1 : SubTypeClassBase
{
public string AaaField { get; set; }
}
public class SubTypeClass2 : SubTypeClassBase
{
public string ZzzField { get; set; }
}
public enum SubType
{
WithAaaField,
WithZzzField
}
[TestMethod]
public void Deserialize()
{
var obj = JsonConvert.DeserializeObject<MainClass>("{\"SubTypeData\":{\"ZzzField\":\"zzz\",\"SubTypeType\":1}}");
Assert.AreEqual("zzz", (obj.SubTypeData as SubTypeClass2)?.ZzzField);
}
这是一个完整的示例,可以使用多态对象读取 和写入 JSON。
假设我们有以下 class 结构:
public class Base {}
public class SubClass1 : Base {
public int field1;
}
public class SubClass2 : Base {
public int field2;
}
我们可以使用自定义转换器,在序列化时在 JSON 中创建一个名为 type
的额外字段,并在反序列化时读取它。
public class PolymorphicJsonConverter : JsonConverter
{
public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
JObject item = JObject.Load(reader);
var type = item["type"].Value<string>();
if (type == "SubClass1") {
return item.ToObject<SubClass1>();
} else if (type == "SubClass2") {
return item.ToObject<SubClass2>();
} else {
return null;
}
}
public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer) {
JObject o = JObject.FromObject(value);
if (value is SubClass1) {
o.AddFirst(new JProperty("type", new JValue("SubClass1")));
} else if (value is SubClass1) {
o.AddFirst(new JProperty("type", new JValue("SubClass2")));
}
o.WriteTo(writer);
}
public override bool CanConvert (Type objectType) {
return typeof(Base).IsAssignableFrom(objectType);
}
}
您可以在容器 class 中使用此转换器,如下所示:
public class Container {
public List<Base> items;
public string Save() {
return JsonConvert.SerializeObject(items, new PolymorphicJsonConverter())
}
public void Load(string jsonText) {
items = JsonConvert.DeserializeObject<List<Base>>(jsonText, new PolymorphicJsonConverter());
}
}
或者您可以使用 JSON.net 的内置类型提示,而不是滚动您自己的 JsonConverter,但它不是那么灵活并且创建非常 un-portable JSON.
JsonConvert.SerializeObject(items, new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.Auto
});
我们希望能够 serialize/deserialize json from/to C# classes,主要 class 有一个多态子实例目的。使用 Json.Net 的 TypeNameHandling.Auto 设置很容易做到这一点。但是,我们希望在没有“$type”字段的情况下这样做。
第一个想法是能够将“$type”重命名为我们选择的值,并使该类型的值成为一个可以正确映射子类型的枚举。我还没有将其视为一种选择,但很高兴听到它是否可行。
第二个想法是沿着以下几行...下面是 classes 的第一遍,顶层 class 有一个关于数据类型的指标 (SubTypeType)包含在子对象 (SubTypeData) 中。我深入研究了 Json.Net 文档并尝试了一些方法,但没有成功。
我们目前可以完全控制数据定义,但一旦部署,事情就被锁定了。
public class MainClass
{
public SubType SubTypeType { get; set; }
public SubTypeClassBase SubTypeData { get; set; }
}
public class SubTypeClassBase
{
}
public class SubTypeClass1 : SubTypeClassBase
{
public string AaaField { get; set; }
}
public class SubTypeClass2 : SubTypeClassBase
{
public string ZzzField { get; set; }
}
在容器 class 中包含子类型信息是有问题的,原因有两个:
- 当 Json.NET 正在读取包含的 class 时无法访问容器 class 实例。
- 如果您以后需要将
SubTypeClassBase
属性 转换成一个列表,那么子类型信息将无处可放。
相反,我建议在基础 class:
中添加子类型信息作为 属性[JsonConverter(typeof(SubTypeClassConverter))]
public class SubTypeClassBase
{
[JsonConverter(typeof(StringEnumConverter))] // Serialize enums by name rather than numerical value
public SubType Type { get { return typeToSubType[GetType()]; } }
}
现在,只要序列化可分配给 SubTypeClassBase
的对象,自定义子类型枚举就会被序列化。完成后,为了反序列化,您可以创建一个 JsonConverter
that loads the json for a given SubTypeClassBase
into a temporary JObject
,检查 "Type"
属性 的值,并将 JSON 对象反序列化为适当的 class。
下面的原型实现:
public enum SubType
{
BaseType,
Type1,
Type2,
}
[JsonConverter(typeof(SubTypeClassConverter))]
public class SubTypeClassBase
{
static readonly Dictionary<Type, SubType> typeToSubType;
static readonly Dictionary<SubType, Type> subTypeToType;
static SubTypeClassBase()
{
typeToSubType = new Dictionary<Type,SubType>()
{
{ typeof(SubTypeClassBase), SubType.BaseType },
{ typeof(SubTypeClass1), SubType.Type1 },
{ typeof(SubTypeClass2), SubType.Type2 },
};
subTypeToType = typeToSubType.ToDictionary(pair => pair.Value, pair => pair.Key);
}
public static Type GetType(SubType subType)
{
return subTypeToType[subType];
}
[JsonConverter(typeof(StringEnumConverter))] // Serialize enums by name rather than numerical value
public SubType Type { get { return typeToSubType[GetType()]; } }
}
public class SubTypeClass1 : SubTypeClassBase
{
public string AaaField { get; set; }
}
public class SubTypeClass2 : SubTypeClassBase
{
public string ZzzField { get; set; }
}
public class SubTypeClassConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(SubTypeClassBase);
}
public override bool CanWrite { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
var typeToken = token["Type"];
if (typeToken == null)
throw new InvalidOperationException("invalid object");
var actualType = SubTypeClassBase.GetType(typeToken.ToObject<SubType>(serializer));
if (existingValue == null || existingValue.GetType() != actualType)
{
var contract = serializer.ContractResolver.ResolveContract(actualType);
existingValue = contract.DefaultCreator();
}
using (var subReader = token.CreateReader())
{
// Using "populate" avoids infinite recursion.
serializer.Populate(subReader, existingValue);
}
return existingValue;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
您可以尝试 JsonSubtypes 支持使用枚举值注册类型映射的转换器实现。
在你的情况下它看起来像这样:
public class MainClass
{
public SubTypeClassBase SubTypeData { get; set; }
}
[JsonConverter(typeof(JsonSubtypes), "SubTypeType")]
[JsonSubtypes.KnownSubType(typeof(SubTypeClass1), SubType.WithAaaField)]
[JsonSubtypes.KnownSubType(typeof(SubTypeClass2), SubType.WithZzzField)]
public class SubTypeClassBase
{
public SubType SubTypeType { get; set; }
}
public class SubTypeClass1 : SubTypeClassBase
{
public string AaaField { get; set; }
}
public class SubTypeClass2 : SubTypeClassBase
{
public string ZzzField { get; set; }
}
public enum SubType
{
WithAaaField,
WithZzzField
}
[TestMethod]
public void Deserialize()
{
var obj = JsonConvert.DeserializeObject<MainClass>("{\"SubTypeData\":{\"ZzzField\":\"zzz\",\"SubTypeType\":1}}");
Assert.AreEqual("zzz", (obj.SubTypeData as SubTypeClass2)?.ZzzField);
}
这是一个完整的示例,可以使用多态对象读取 和写入 JSON。
假设我们有以下 class 结构:
public class Base {}
public class SubClass1 : Base {
public int field1;
}
public class SubClass2 : Base {
public int field2;
}
我们可以使用自定义转换器,在序列化时在 JSON 中创建一个名为 type
的额外字段,并在反序列化时读取它。
public class PolymorphicJsonConverter : JsonConverter
{
public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
JObject item = JObject.Load(reader);
var type = item["type"].Value<string>();
if (type == "SubClass1") {
return item.ToObject<SubClass1>();
} else if (type == "SubClass2") {
return item.ToObject<SubClass2>();
} else {
return null;
}
}
public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer) {
JObject o = JObject.FromObject(value);
if (value is SubClass1) {
o.AddFirst(new JProperty("type", new JValue("SubClass1")));
} else if (value is SubClass1) {
o.AddFirst(new JProperty("type", new JValue("SubClass2")));
}
o.WriteTo(writer);
}
public override bool CanConvert (Type objectType) {
return typeof(Base).IsAssignableFrom(objectType);
}
}
您可以在容器 class 中使用此转换器,如下所示:
public class Container {
public List<Base> items;
public string Save() {
return JsonConvert.SerializeObject(items, new PolymorphicJsonConverter())
}
public void Load(string jsonText) {
items = JsonConvert.DeserializeObject<List<Base>>(jsonText, new PolymorphicJsonConverter());
}
}
或者您可以使用 JSON.net 的内置类型提示,而不是滚动您自己的 JsonConverter,但它不是那么灵活并且创建非常 un-portable JSON.
JsonConvert.SerializeObject(items, new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.Auto
});