具有多态子对象的Json.Net序列化

时间:2015-04-09 02:20:30

标签: c# serialization json.net

我们希望能够从/向C#类序列化/反序列化json,主类具有多态子对象的实例。使用Json.Net的TypeNameHandling.Auto设置很容易做到这一点。但是,如果没有" $ type"我们希望这样做。字段。

首先想到的是能够重命名" $ type"到我们选择的值,并使该类型的值是一个能够正确映射子类型的枚举。我没有看到这是一个选项,但很高兴听到它是否可能。

第二个想法是沿着以下几行......下面是类的第一次传递,顶级类具有关于子对象(SubTypeData)中包含的数据类型的指示符(SubTypeType)。我已经在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; }
}

3 个答案:

答案 0 :(得分:11)

在容器类中包含子类型信息存在问题,原因有两个:

  1. 当Json.NET读取包含的类时,无法访问容器类实例。
  2. 如果您以后需要将SubTypeClassBase属性转换为列表,则无法放置子类型信息。
  3. 相反,我建议将子类型信息添加为基类中的属性:

    [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,将给定SubTypeClassBase的json加载到临时JObject中,检查"Type"属性的值,并反序列化JSON对象作为适当的类。

    下面的原型实现:

    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();
        }
    }
    

答案 1 :(得分:1)

您可以尝试使用支持使用枚举值注册类型映射的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);
        }

答案 2 :(得分:1)

这是一个完整的示例,可以使用多态对象读取和写 JSON。

假设我们有以下类结构:

public class Base {}
public class SubClass1 : Base {
    public int field1;
}
public class SubClass2 : Base {
    public int field2;
}

我们可以使用自定义转换器在序列化时在名为type的JSON中创建额外字段,并在反序列化时读取它。

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);
    }
}

您可以在容器类中使用此转换器,如下所示:

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,但它不是那么灵活,并且创建了非常不可移植的JSON。

JsonConvert.SerializeObject(items, new JsonSerializerSettings {
    TypeNameHandling = TypeNameHandling.Auto
});