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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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返回列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[
  {
   "Department":"Department1",
   "JobTitle":"JobTitle1",
   "FirstName":"FirstName1",
   "LastName":"LastName1"
  },
  {
   "Department":"Department2",
   "JobTitle":"JobTitle2",
   "FirstName":"FirstName2",
   "LastName":"LastName2"
  },
  {
   "Skill":"Painter",
   "FirstName":"FirstName3",
   "LastName":"LastName3"
  }
]

我不想使用类型名处理JSonserializerSettings。我专门寻找定制的JSonConverter实现来处理这个问题。关于这方面的文档和示例在网络上非常稀少。我似乎无法在jsonConverter中获得重写的readjson()方法实现。


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

我找到了一个关于类型转换的讨论线程,结果提供了答案。这里有一个链接:类型转换。

需要的是将JsonConverter子类化,覆盖ReadJson方法,创建一个新的抽象Create方法,接受JObject

The JObject class provides a means to load a JSON object and
provides access to the data within this object.

重写的ReadJson方法创建JObject并调用Create方法(由派生的converter类实现),传入JObject实例。

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

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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;
    }
}


上述针对JsonCreationConverter的解决方案遍布互联网,但有一个缺陷,在极少数情况下会显现出来。在readjson方法中创建的新jsonreader不继承任何原始读卡器的配置值(区域性、日期分析处理、日期时区处理、floatParseHandling等)。在serializer.populate()中使用新的jsonreader之前,应该复制这些值。

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

更新我更新了这个,以有一个更明确的方法,使现有的读者副本。这只是封装了通过单个JSonReader设置进行复制的过程。理想情况下,此功能将在NewtonSoft库中维护,但现在,您可以使用以下功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <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;
}

应按如下方式使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

旧的解决方案如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/// <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);
    }
}


我想我也会共享一个基于这个的解决方案,它使用反射与knownType属性一起工作,必须从任何基类中获取派生类,解决方案可以从递归中受益,以找到最佳匹配类,尽管我不需要它,在我的情况下,匹配是由给定给转换器的类型来完成的,如果它有knownType,它将sc在匹配JSON字符串中具有所有属性的类型之前,首先选择要匹配的类型。

使用简单如下:

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

在上述情况下,RET应为B型。

JSON类:

1
2
3
4
5
6
7
8
9
10
[KnownType(typeof(B))]
public class A
{
   public string Name { get; set; }
}

public class B : A
{
   public string LastName { get; set; }
}

转换器代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/// <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();
        }
    }


JSonSubtypes项目实现了一个通用的转换器,它借助于属性来处理这个特性。

这里提供的混凝土样本是如何工作的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    [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\
<div class="suo-content">[collapse title=""]<ul><li>非常有用的转换器。我自己就省了几个小时编写转换器的时间!</li></ul>[/collapse]</div><hr><P>这是图腾答案的扩展。它基本上做了相同的事情,但是属性匹配是基于序列化的JSON对象,而不是反映.NET对象。如果您正在使用[json属性]、使用camelcasepropertynamesControlResolver或执行任何其他操作,都会导致json与.NET对象不匹配,则这一点非常重要。</P><P>用法很简单:</P>[cc lang="csharp"][KnownType(typeof(B))]
public class A
{
   public string Name { get; set; }
}

public class B : A
{
   public string LastName { get; set; }
}

转换器代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/// <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;
    }
}

作为图腾已知类型解决方案的另一个变体,您可以使用反射来创建通用类型分解器,以避免使用已知类型属性。

这使用了一种类似于JuvalLowy的WCF通用解析器的技术。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
 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();
    }

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
    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");
    }
}

因此,您可以像这样在全球范围内包括:

1
2
3
4
5
6
7
public static JsonSerializerSettings StandardSerializerSettings => new JsonSerializerSettings
    {
        Converters = new List<JsonConverter>
        {
            new InterfaceConverter()
        }
    };

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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);
    }
}