如何将json对象反序列化为特定的子类?

时间:2016-04-12 21:08:20

标签: c# json deserialization

我有一个包含Row对象列表的Cabin类。我希望像这样序列化对象,但是在反序列化时,我希望Row对象是RowRule对象,它继承自Row对象。以下是我尝试过的一些示例代码。

class Program
{
    static void Main(string[] args)
    {
        var cabin = new Cabin();
        var row = new Row();
        row.Status = "Success";
        cabin.Rows = new List<Row>()
        {
            row,
            row
        };

        JsonSerializerSettings settings = new JsonSerializerSettings()
        {
            TypeNameHandling = TypeNameHandling.Auto
        };
        string json = JsonConvert.SerializeObject(cabin, Formatting.Indented, settings);
        Console.WriteLine(json);

        Cabin obj = JsonConvert.DeserializeObject<Cabin>(json,
            new JsonSerializerSettings() {TypeNameHandling = TypeNameHandling.Auto});
        Console.WriteLine(obj);
        Debug.Assert(obj.Rows.First().GetType().Name == "RowRule");
    }
}

class Cabin
{
    public IList<Row> Rows { get; set; } 
}

class Row
{
    public string Status { get; set; }
}

class RowRule : Row
{

}

2 个答案:

答案 0 :(得分:3)

简单的答案是使用CustomCreationConverter<Row>并从Create()返回class RowToRoleRuleConverter : CustomCreationConverter<Row> { public override Row Create(Type objectType) { if (objectType.IsAssignableFrom(typeof(RowRule))) return Activator.CreateInstance<RowRule>(); return (Row)Activator.CreateInstance(objectType); } }

"$type"

但是,您使用的是TypeNameHandling.Auto,这意味着您的JSON中可能存在多态CustomCreationConverter<T>属性。不幸的是,DowncastingConverter<TBase, TDerived>忽略了这些属性。因此,有必要做一些额外的工作并创建public class DowncastingConverter<TBase, TDerived> : PolymorphicCreationConverter<TBase> where TDerived : TBase { protected override TBase Create(Type objectType, Type polymorphicType, object existingValue, IContractResolver contractResolver, JObject obj) { Type createType = objectType; if (createType.IsAssignableFrom(polymorphicType)) createType = polymorphicType; if (createType.IsAssignableFrom(typeof(TDerived))) createType = typeof(TDerived); if (existingValue != null && createType.IsAssignableFrom(existingValue.GetType())) return (TBase)existingValue; var contract = contractResolver.ResolveContract(createType); return (TBase)contract.DefaultCreator(); } } public abstract class PolymorphicCreationConverter<T> : JsonConverter { public override bool CanConvert(Type objectType) { return typeof(T).IsAssignableFrom(objectType); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotSupportedException("CustomCreationConverter should only be used while deserializing."); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; var obj = JObject.Load(reader); Type polymorphicType = null; var polymorphicTypeString = (string)obj["$type"]; if (polymorphicTypeString != null) { if (serializer.TypeNameHandling != TypeNameHandling.None) { string typeName, assemblyName; ReflectionUtils.SplitFullyQualifiedTypeName(polymorphicTypeString, out typeName, out assemblyName); polymorphicType = serializer.Binder.BindToType(assemblyName, typeName); } obj.Remove("$type"); } var value = Create(objectType, polymorphicType, existingValue, serializer.ContractResolver, obj); if (value == null) throw new JsonSerializationException("No object created."); using (var subReader = obj.CreateReader()) serializer.Populate(subReader, value); return value; } protected abstract T Create(Type objectType, Type polymorphicType, object existingValue, IContractResolver iContractResolver, JObject obj); public override bool CanWrite { get { return false; } } } internal static class ReflectionUtils { // Utilities taken from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs // I couldn't find a way to access these directly. public static void SplitFullyQualifiedTypeName(string fullyQualifiedTypeName, out string typeName, out string assemblyName) { int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName); if (assemblyDelimiterIndex != null) { typeName = fullyQualifiedTypeName.Substring(0, assemblyDelimiterIndex.GetValueOrDefault()).Trim(); assemblyName = fullyQualifiedTypeName.Substring(assemblyDelimiterIndex.GetValueOrDefault() + 1, fullyQualifiedTypeName.Length - assemblyDelimiterIndex.GetValueOrDefault() - 1).Trim(); } else { typeName = fullyQualifiedTypeName; assemblyName = null; } } private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName) { int scope = 0; for (int i = 0; i < fullyQualifiedTypeName.Length; i++) { char current = fullyQualifiedTypeName[i]; switch (current) { case '[': scope++; break; case ']': scope--; break; case ',': if (scope == 0) { return i; } break; } } return null; } }

JsonSerializerSettings readSettings = new JsonSerializerSettings()
{
    TypeNameHandling = TypeNameHandling.Auto,
    Converters = new[] { new DowncastingConverter<Row, RowRule>() },
};
Cabin obj = JsonConvert.DeserializeObject<Cabin>(json, readSettings);

然后使用它:

TypeNameHandling

原型fiddle

最后,在使用UIButton时,请注意Newtonsoft docs中的这一注意事项:

  

当您的应用程序从外部源反序列化JSON时,应谨慎使用TypeNameHandling。使用非None以外的值进行反序列化时,应使用自定义SerializationBinder验证传入类型。

有关可能需要执行此操作的讨论,请参阅 TypeNameHandling caution in Newtonsoft Json

答案 1 :(得分:0)

示例代码的问题是,您正在创建Row的对象并尝试获取RowRule这是不可能的。

可能你想这样做:

        var cabin = new Cabin();
        var row = new RowRule(); // create derived object
        row.Status = "Success";
        cabin.Rows = new List<Row>()
        {
          row,
          row
        };