How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?

前端 未结 9 1824
星月不相逢
星月不相逢 2020-11-21 05:07

I am trying to extend the JSON.net example given here http://james.newtonking.com/projects/json/help/CustomCreationConverter.html

I have another sub class deriving

相关标签:
9条回答
  • 2020-11-21 06:06

    The project JsonSubTypes implement a generic converter that handle this feature with the helps of attributes.

    For the concrete sample provided here is how it works:

        [JsonConverter(typeof(JsonSubtypes))]
        [JsonSubtypes.KnownSubTypeWithProperty(typeof(Employee), "JobTitle")]
        [JsonSubtypes.KnownSubTypeWithProperty(typeof(Artist), "Skill")]
        public class Person
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
        }
    
        public class Employee : Person
        {
            public string Department { get; set; }
            public string JobTitle { get; set; }
        }
    
        public class Artist : Person
        {
            public string Skill { get; set; }
        }
    
        [TestMethod]
        public void Demo()
        {
            string json = "[{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
                          "{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
                          "{\"Skill\":\"Painter\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}]";
    
    
            var persons = JsonConvert.DeserializeObject<IReadOnlyCollection<Person>>(json);
            Assert.AreEqual("Painter", (persons.Last() as Artist)?.Skill);
        }
    
    0 讨论(0)
  • 2020-11-21 06:08

    Just thought i would share a solution also based on this that works with the Knowntype attribute using reflection , had to get derived class from any base class, solution can benefit from recursion to find the best matching class though i didn't need it in my case, matching is done by the type given to the converter if it has KnownTypes it will scan them all until it matches a type that has all the properties inside the json string, first one to match will be chosen.

    usage is as simple as:

     string json = "{ Name:\"Something\", LastName:\"Otherthing\" }";
     var ret  = JsonConvert.DeserializeObject<A>(json, new KnownTypeConverter());
    

    in the above case ret will be of type B.

    JSON classes:

    [KnownType(typeof(B))]
    public class A
    {
       public string Name { get; set; }
    }
    
    public class B : A
    {
       public string LastName { get; set; }
    }
    

    Converter code:

    /// <summary>
        /// Use KnownType Attribute to match a divierd class based on the class given to the serilaizer
        /// Selected class will be the first class to match all properties in the json object.
        /// </summary>
        public  class KnownTypeConverter : JsonConverter
        {
            public override bool CanConvert(Type objectType)
            {
                return System.Attribute.GetCustomAttributes(objectType).Any(v => v is KnownTypeAttribute);
            }
    
            public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
            {
                // Load JObject from stream
                JObject jObject = JObject.Load(reader);
    
                // Create target object based on JObject
                System.Attribute[] attrs = System.Attribute.GetCustomAttributes(objectType);  // Reflection. 
    
                    // Displaying output. 
                foreach (System.Attribute attr in attrs)
                {
                    if (attr is KnownTypeAttribute)
                    {
                        KnownTypeAttribute k = (KnownTypeAttribute) attr;
                        var props = k.Type.GetProperties();
                        bool found = true;
                        foreach (var f in jObject)
                        {
                            if (!props.Any(z => z.Name == f.Key))
                            {
                                found = false;
                                break;
                            }
                        }
    
                        if (found)
                        {
                            var target = Activator.CreateInstance(k.Type);
                            serializer.Populate(jObject.CreateReader(),target);
                            return target;
                        }
                    }
                }
                throw new ObjectNotFoundException();
    
    
                // Populate the object properties
    
            }
    
            public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
            {
                throw new NotImplementedException();
            }
        }
    
    0 讨论(0)
  • 2020-11-21 06:10

    A lot of the times the implementation will exist in the same namespace as the interface. So, I came up with this:

        public class InterfaceConverter : JsonConverter
        {
        public override bool CanWrite => false;
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var token = JToken.ReadFrom(reader);
            var typeVariable = this.GetTypeVariable(token);
            if (TypeExtensions.TryParse(typeVariable, out var implimentation))
            { }
            else if (!typeof(IEnumerable).IsAssignableFrom(objectType))
            {
                implimentation = this.GetImplimentedType(objectType);
            }
            else
            {
                var genericArgumentTypes = objectType.GetGenericArguments();
                var innerType = genericArgumentTypes.FirstOrDefault();
                if (innerType == null)
                {
                    implimentation = typeof(IEnumerable);
                }
                else
                {
                    Type genericType = null;
                    if (token.HasAny())
                    {
                        var firstItem = token[0];
                        var genericTypeVariable = this.GetTypeVariable(firstItem);
                        TypeExtensions.TryParse(genericTypeVariable, out genericType);
                    }
    
                    genericType = genericType ?? this.GetImplimentedType(innerType);
                    implimentation = typeof(IEnumerable<>);
                    implimentation = implimentation.MakeGenericType(genericType);
                }
            }
    
            return JsonConvert.DeserializeObject(token.ToString(), implimentation);
        }
    
        public override bool CanConvert(Type objectType)
        {
            return !typeof(IEnumerable).IsAssignableFrom(objectType) && objectType.IsInterface || typeof(IEnumerable).IsAssignableFrom(objectType) && objectType.GetGenericArguments().Any(t => t.IsInterface);
        }
    
        protected Type GetImplimentedType(Type interfaceType)
        {
            if (!interfaceType.IsInterface)
            {
                return interfaceType;
            }
    
            var implimentationQualifiedName = interfaceType.AssemblyQualifiedName?.Replace(interfaceType.Name, interfaceType.Name.Substring(1));
            return implimentationQualifiedName == null ? interfaceType : Type.GetType(implimentationQualifiedName) ?? interfaceType;
        }
    
        protected string GetTypeVariable(JToken token)
        {
            if (!token.HasAny())
            {
                return null;
            }
    
            return token.Type != JTokenType.Object ? null : token.Value<string>("$type");
        }
    }
    

    Therefore, you can include this globally like so:

    public static JsonSerializerSettings StandardSerializerSettings => new JsonSerializerSettings
        {
            Converters = new List<JsonConverter>
            {
                new InterfaceConverter()
            }
        };
    
    0 讨论(0)
提交回复
热议问题