使用Json.Net序列化具有多态子对象的类型

21
我们希望能够将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; }
}
3个回答

21

将子类型信息放在容器类中存在两个问题:

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

这正是我想要做的! - GaTechThomas
谢谢!那个无限递归部分让我崩溃了! - Joseph
我从这里改编了我的解决方案,然后它就起作用了。写得很好! - Raz Friman

6
这里有一个完整的示例,可以使用多态对象读取和编写JSON。假设我们有以下类结构:
public class Base {}
public class SubClass1 : Base {
    public int field1;
}
public class SubClass2 : Base {
    public int field2;
}

我们可以使用自定义转换器,在序列化时创建一个名为 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
});

5
您可以尝试使用支持使用枚举值注册类型映射的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
存在一个名为 NuGet 的 https://www.nuget.org/packages/JsonSubTypes。可以在 JsonSerializerSettings 中进行配置,而无需触及模型类。太好了! - r2d2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接