How to deserialize json objects into specific subclasses?

后端 未结 2 854
伪装坚强ぢ
伪装坚强ぢ 2020-12-11 10:52

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 objec

相关标签:
2条回答
  • 2020-12-11 11:37

    The problem with your sample code is, you're creating object of Row and trying to get RowRule which is not possible.

    May be you wanted to do it like this:

            var cabin = new Cabin();
            var row = new RowRule(); // create derived object
            row.Status = "Success";
            cabin.Rows = new List<Row>()
            {
              row,
              row
            };
    
    0 讨论(0)
  • 2020-12-11 11:44

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

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

    Then use it like:

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

    Prototype fiddle.

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

    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.

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

    0 讨论(0)
提交回复
热议问题