首页 文章

如何在JSON.NET中实现自定义JsonConverter来反序列化基类对象的列表?

提问于
浏览
267

我试图扩展这里给出的JSON.net示例http://james.newtonking.com/projects/json/help/CustomCreationConverter.html

我有另一个派生自基类/接口的子类

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

List<Person> people  = new List<Person>
{
    new Employee(),
    new Employee(),
    new Artist(),
};

如何将Json反序列化回List <Person>

[
  {
    "Department": "Department1",
    "JobTitle": "JobTitle1",
    "FirstName": "FirstName1",
    "LastName": "LastName1"
  },
  {
    "Department": "Department2",
    "JobTitle": "JobTitle2",
    "FirstName": "FirstName2",
    "LastName": "LastName2"
  },
  {
    "Skill": "Painter",
    "FirstName": "FirstName3",
    "LastName": "LastName3"
  }
]

我不想使用TypeNameHandling JsonSerializerSettings . 我特意寻找自定义JsonConverter实现来处理这个问题 . 网络上的文档和示例非常稀少 . 我似乎无法在JsonConverter中获得重写的ReadJson()方法实现 .

8 回答

  • 289

    使用标准 CustomCreationConverter ,我正在努力工作如何生成正确的类型( PersonEmployee ),因为为了确定这一点,您需要分析JSON,并且没有内置的方法来使用 Create 方法执行此操作 .

    我找到了一个与类型转换有关的讨论主题,结果却提供了答案 . 这是一个链接:Type converting .

    所需要的是子类 JsonConverter ,重写 ReadJson 方法并创建一个接受 JObject 的新抽象 Create 方法 .

    JObject类提供了一种加载JSON对象的方法,并提供对此对象中数据的访问 .

    重写的 ReadJson 方法创建 JObject 并调用 Create 方法(由我们派生的转换器类实现),传入 JObject 实例 .

    然后可以通过检查某些字段的存在来分析该 JObject 实例以确定正确的类型 .

    Example

    string json = "[{
            \"Department\": \"Department1\",
            \"JobTitle\": \"JobTitle1\",
            \"FirstName\": \"FirstName1\",
            \"LastName\": \"LastName1\"
        },{
            \"Department\": \"Department2\",
            \"JobTitle\": \"JobTitle2\",
            \"FirstName\": \"FirstName2\",
            \"LastName\": \"LastName2\"
        },
            {\"Skill\": \"Painter\",
            \"FirstName\": \"FirstName3\",
            \"LastName\": \"LastName3\"
        }]";
    
    List<Person> persons = 
        JsonConvert.DeserializeObject<List<Person>>(json, new PersonConverter());
    
    ...
    
    public class PersonConverter : JsonCreationConverter<Person>
    {
        protected override Person Create(Type objectType, JObject jObject)
        {
            if (FieldExists("Skill", jObject))
            {
                return new Artist();
            }
            else if (FieldExists("Department", jObject))
            {
                return new Employee();
            }
            else
            {
                return new Person();
            }
        }
    
        private bool FieldExists(string fieldName, JObject jObject)
        {
            return jObject[fieldName] != null;
        }
    }
    
    public abstract class JsonCreationConverter<T> : JsonConverter
    {
        /// <summary>
        /// Create an instance of objectType, based properties in the JSON object
        /// </summary>
        /// <param name="objectType">type of object expected</param>
        /// <param name="jObject">
        /// contents of JSON object that will be deserialized
        /// </param>
        /// <returns></returns>
        protected abstract T Create(Type objectType, JObject jObject);
    
        public override bool CanConvert(Type objectType)
        {
            return typeof(T).IsAssignableFrom(objectType);
        }
    
        public override bool CanWrite
        {
            get { return false; }
        }
    
        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
            T target = Create(objectType, jObject);
    
            // Populate the object properties
            serializer.Populate(jObject.CreateReader(), target);
    
            return target;
        }
    }
    
  • 7

    以上针对 JsonCreationConverter<T> 的解决方案遍布互联网,但有一个缺陷在极少数情况下表现出来 . 在ReadJson方法中创建的新JsonReader不会继承任何原始读者的配置值(Culture,DateParseHandling,DateTimeZoneHandling,FloatParseHandling等等) . 在serializer.Populate()中使用新的JsonReader之前,应复制这些值 .

    这是我能解决上述实现中的一些问题的最好方法,但我仍然认为有些事情被忽略了:

    Update 我对此进行了更新,以便有一个更明确的方法来制作现有阅读器的副本 . 这只是封装了复制单个JsonReader设置的过程 . 理想情况下,此功能将在Newtonsoft库本身中维护,但是现在,您可以使用以下内容:

    /// <summary>Creates a new reader for the specified jObject by copying the settings
    /// from an existing reader.</summary>
    /// <param name="reader">The reader whose settings should be copied.</param>
    /// <param name="jObject">The jObject to create a new reader for.</param>
    /// <returns>The new disposable reader.</returns>
    public static JsonReader CopyReaderForObject(JsonReader reader, JObject jObject)
    {
        JsonReader jObjectReader = jObject.CreateReader();
        jObjectReader.Culture = reader.Culture;
        jObjectReader.DateFormatString = reader.DateFormatString;
        jObjectReader.DateParseHandling = reader.DateParseHandling;
        jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
        jObjectReader.FloatParseHandling = reader.FloatParseHandling;
        jObjectReader.MaxDepth = reader.MaxDepth;
        jObjectReader.SupportMultipleContent = reader.SupportMultipleContent;
        return jObjectReader;
    }
    

    这应该如下使用:

    public override object ReadJson(JsonReader reader,
                                    Type objectType,
                                    object existingValue,
                                    JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);
        // Create target object based on JObject
        T target = Create(objectType, jObject);
        // Populate the object properties
        using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
        {
            serializer.Populate(jObjectReader, target);
        }
        return target;
    }
    

    Older solution follows:

    /// <summary>Base Generic JSON Converter that can help quickly define converters for specific types by automatically
    /// generating the CanConvert, ReadJson, and WriteJson methods, requiring the implementer only to define a strongly typed Create method.</summary>
    public abstract class JsonCreationConverter<T> : JsonConverter
    {
        /// <summary>Create an instance of objectType, based properties in the JSON object</summary>
        /// <param name="objectType">type of object expected</param>
        /// <param name="jObject">contents of JSON object that will be deserialized</param>
        protected abstract T Create(Type objectType, JObject jObject);
    
        /// <summary>Determines if this converted is designed to deserialization to objects of the specified type.</summary>
        /// <param name="objectType">The target type for deserialization.</param>
        /// <returns>True if the type is supported.</returns>
        public override bool CanConvert(Type objectType)
        {
            // FrameWork 4.5
            // return typeof(T).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
            // Otherwise
            return typeof(T).IsAssignableFrom(objectType);
        }
    
        /// <summary>Parses the json to the specified type.</summary>
        /// <param name="reader">Newtonsoft.Json.JsonReader</param>
        /// <param name="objectType">Target type.</param>
        /// <param name="existingValue">Ignored</param>
        /// <param name="serializer">Newtonsoft.Json.JsonSerializer to use.</param>
        /// <returns>Deserialized Object</returns>
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
    
            // Load JObject from stream
            JObject jObject = JObject.Load(reader);
    
            // Create target object based on JObject
            T target = Create(objectType, jObject);
    
            //Create a new reader for this jObject, and set all properties to match the original reader.
            JsonReader jObjectReader = jObject.CreateReader();
            jObjectReader.Culture = reader.Culture;
            jObjectReader.DateParseHandling = reader.DateParseHandling;
            jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
            jObjectReader.FloatParseHandling = reader.FloatParseHandling;
    
            // Populate the object properties
            serializer.Populate(jObjectReader, target);
    
            return target;
        }
    
        /// <summary>Serializes to the specified type</summary>
        /// <param name="writer">Newtonsoft.Json.JsonWriter</param>
        /// <param name="value">Object to serialize.</param>
        /// <param name="serializer">Newtonsoft.Json.JsonSerializer to use.</param>
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value);
        }
    }
    
  • 12

    只是想我会分享一个基于此的解决方案,使用反射的Knowntype属性,必须从任何基类获取派生类,解决方案可以受益于递归找到最佳匹配类虽然我不需要它在我的例如,匹配是通过给转换器的类型完成的,如果它具有KnownTypes,它将扫描所有类型,直到它匹配具有json字符串内所有属性的类型,首先选择要匹配的类型 .

    用法很简单:

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

    在上述情况下,ret将是B型 .

    JSON类:

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

    转换器代码:

    /// <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

    这是对图腾答案的扩展 . 它基本上做了同样的事情,但属性匹配基于序列化的json对象,而不是反映.net对象 . 如果你使用[JsonProperty],使用CamelCasePropertyNamesContractResolver,或者做任何会导致json与.net对象不匹配的事情,这一点很重要 .

    用法很简单:

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

    转换器代码:

    /// <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 bool CanWrite {
            get { return false; }
        }
    
        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. 
    
            // check known types for a match. 
            foreach( var attr in attrs.OfType<KnownTypeAttribute>( ) ) {
                object target = Activator.CreateInstance( attr.Type );
    
                JObject jTest;
                using( var writer = new StringWriter( ) ) {
                    using( var jsonWriter = new JsonTextWriter( writer ) ) {
                        serializer.Serialize( jsonWriter, target );
                        string json = writer.ToString( );
                        jTest = JObject.Parse( json );
                    }
                }
    
                var jO = this.GetKeys( jObject ).Select( k => k.Key ).ToList( );
                var jT = this.GetKeys( jTest ).Select( k => k.Key ).ToList( );
    
                if( jO.Count == jT.Count && jO.Intersect( jT ).Count( ) == jO.Count ) {
                    serializer.Populate( jObject.CreateReader( ), target );
                    return target;
                }
            }
    
            throw new SerializationException( string.Format( "Could not convert base class {0}", objectType ) );
        }
    
        public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ) {
            throw new NotImplementedException( );
        }
    
        private IEnumerable<KeyValuePair<string, JToken>> GetKeys( JObject obj ) {
            var list = new List<KeyValuePair<string, JToken>>( );
            foreach( var t in obj ) {
                list.Add( t );
            }
            return list;
        }
    }
    
  • 87

    作为Totem已知类型解决方案的另一种变体,您可以使用反射创建泛型类型解析器,以避免使用已知类型属性 .

    这对于WCF使用类似于Juval Lowy's GenericResolver的技术 .

    只要您的基类是抽象的或接口,就会自动确定已知类型,而不必使用已知类型属性进行修饰 .

    在我自己的情况下,我选择使用$ type属性来指定我的json对象中的类型,而不是尝试从属性中确定它,尽管您可以从这里借用其他解决方案来使用基于属性的确定 .

    public class JsonKnownTypeConverter : JsonConverter
    {
        public IEnumerable<Type> KnownTypes { get; set; }
    
        public JsonKnownTypeConverter() : this(ReflectTypes())
        {
    
        }
        public JsonKnownTypeConverter(IEnumerable<Type> knownTypes)
        {
            KnownTypes = knownTypes;
        }
    
        protected object Create(Type objectType, JObject jObject)
        {
            if (jObject["$type"] != null)
            {
                string typeName = jObject["$type"].ToString();
                return Activator.CreateInstance(KnownTypes.First(x => typeName == x.Name));
            }
            else
            {
                return Activator.CreateInstance(objectType);
            }
            throw new InvalidOperationException("No supported type");
        }
    
        public override bool CanConvert(Type objectType)
        {
            if (KnownTypes == null)
                return false;
    
            return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom);
        }
    
        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
            var target = Create(objectType, jObject);
            // Populate the object properties
            serializer.Populate(jObject.CreateReader(), target);
            return target;
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    
        //Static helpers
        static Assembly CallingAssembly = Assembly.GetCallingAssembly();
    
        static Type[] ReflectTypes()
        {
            List<Type> types = new List<Type>();
            var referencedAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
            foreach (var assemblyName in referencedAssemblies)
            {
                Assembly assembly = Assembly.Load(assemblyName);
                Type[] typesInReferencedAssembly = GetTypes(assembly);
                types.AddRange(typesInReferencedAssembly);
            }
    
            return types.ToArray();
        }
    
        static Type[] GetTypes(Assembly assembly, bool publicOnly = true)
        {
            Type[] allTypes = assembly.GetTypes();
    
            List<Type> types = new List<Type>();
    
            foreach (Type type in allTypes)
            {
                if (type.IsEnum == false &&
                   type.IsInterface == false &&
                   type.IsGenericTypeDefinition == false)
                {
                    if (publicOnly == true && type.IsPublic == false)
                    {
                        if (type.IsNested == false)
                        {
                            continue;
                        }
                        if (type.IsNestedPrivate == true)
                        {
                            continue;
                        }
                    }
                    types.Add(type);
                }
            }
            return types.ToArray();
        }
    

    然后可以将其安装为格式化程序

    GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new JsonKnownTypeConverter());
    
  • 1

    项目JsonSubTypes实现了一个通用转换器,可以通过属性帮助处理此功能 .

    对于这里提供的具体样本是如何工作的:

    [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);
        }
    
  • 5

    这是另一个避免使用 jObject.CreateReader() 的解决方案,而是创建一个新的 JsonTextReader (这是默认 JsonCreate.Deserialze 方法使用的行为:

    public abstract class JsonCreationConverter<T> : JsonConverter
    {
        protected abstract T Create(Type objectType, JObject jObject);
    
        public override bool CanConvert(Type objectType)
        {
            return typeof(T).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
    
            // Load JObject from stream
            JObject jObject = JObject.Load(reader);
    
            // Create target object based on JObject
            T target = Create(objectType, jObject);
    
            // Populate the object properties
            StringWriter writer = new StringWriter();
            serializer.Serialize(writer, jObject);
            using (JsonTextReader newReader = new JsonTextReader(new StringReader(writer.ToString())))
            { 
                newReader.Culture = reader.Culture;
                newReader.DateParseHandling = reader.DateParseHandling;
                newReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
                newReader.FloatParseHandling = reader.FloatParseHandling;
                serializer.Populate(newReader, target);
            }
    
            return target;
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value);
        }
    }
    
  • 5

    很多时候,实现将存在于与接口相同的命名空间中 . 所以,我想出了这个:

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

    因此,您可以像这样全局包含:

    public static JsonSerializerSettings StandardSerializerSettings => new JsonSerializerSettings
        {
            Converters = new List<JsonConverter>
            {
                new InterfaceConverter()
            }
        };
    

相关问题