【问题标题】:Json.Net Serialization of Type with Polymorphic Child Object具有多态子对象的类型的 Json.Net 序列化
【发布时间】:2015-04-09 02:20:29
【问题描述】:

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

第一个想法是能够将“$type”重命名为我们选择的值,并将该类型的值设置为可以正确映射子类型的枚举。我不认为这是一种选择,但如果可能的话,我很高兴听到。

第二个想法是沿着以下几行......下面是类的第一次传递,顶级类具有一个指示符(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; }
}

【问题讨论】:

    标签: c# serialization json.net


    【解决方案1】:

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

    1. 当 Json.NET 正在读取包含的类时,无法访问容器类实例。
    2. 如果您以后需要将SubTypeClassBase 属性转换为例如列表,则将无处放置子类型信息。

    相反,我建议将子类型信息作为属性添加到基类中:

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

    【讨论】:

    • 这正是我想要做的!
    • 谢谢!那个无限递归部分让我死了!
    • 我根据这个调整了我的解决方案,它奏效了。写得真好!
    【解决方案2】:

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

    【讨论】:

    【解决方案3】:

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

    假设我们有以下类结构:

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

    你可以像这样在容器类中使用这个转换器:

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

    【讨论】:

      猜你喜欢
      • 2014-06-15
      • 2015-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-18
      相关资源
      最近更新 更多