如何将json对象反序列化为特定的子类? [英] How to deserialize json objects into specific subclasses?

查看:139
本文介绍了如何将json对象反序列化为特定的子类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Cabin类,其中包含Row对象的列表.我想像这样序列化对象,但是在反序列化时,我希望Row对象是从Row对象继承的RowRule对象.下面是我一直在尝试的一些示例代码.

I have a Cabin class that contains a list of Row objects. I'd like to serialize the objects like this, but when deserializing I'd like the Row objects to be RowRule objects, which inherit from the Row object. Below is some sample code I've been trying.

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
{

}

推荐答案

简单的答案是使用 CustomCreationConverter<Row> ,然后从RowRule" rel ="nofollow noreferrer > Create() :

The simple answer is to use a CustomCreationConverter<Row> and return a RowRule from 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);
    }
}

但是,您正在使用 TypeNameHandling.Auto JSON中可能有多态"$type"属性.不幸的是,CustomCreationConverter<T>忽略了这些属性.因此,有必要做一些额外的工作并创建DowncastingConverter<TBase, TDerived>:

However, you are using TypeNameHandling.Auto which implies that there may be polymorphic "$type" properties in your JSON. Unfortunately, CustomCreationConverter<T> ignores these properties. Thus it will be necessary to do some additional work and create 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时,请从 Newtonsoft文档:

Finally, when using TypeNameHandling, do take note of this caution from the Newtonsoft docs:

当您的应用程序从外部源反序列化JSON时,应谨慎使用

TypeNameHandling.反序列化除None以外的其他值时,应使用自定义SerializationBinder验证传入的类型.

TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.

有关为什么可能需要这样做的讨论,请参见 Newtonsoft Json中的TypeNameHandling警告

For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json.

这篇关于如何将json对象反序列化为特定的子类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆