我试图扩展这里给出的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回到列表<人>

[
  {
    "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实现来处理这个问题。关于这方面的文档和示例在网上非常少。我似乎不能得到覆盖的ReadJson()方法实现在JsonConverter的权利。


当前回答

使用图腾和zlangner的思想,我创建了一个KnownTypeConverter,它将能够确定最合适的继承程序,同时考虑到json数据可能没有可选元素。

因此,服务发送一个包含文档数组(传入和传出)的JSON响应。文档既有公共的元素集,也有不同的元素集。 在这种情况下,与外发文档相关的元素是可选的,可以不存在。

在这方面,创建了一个基类Document,其中包含一组公共属性。 还创建了两个继承类: OutgoingDocument增加了两个可选元素“device_id”和“msg_id”; - IncomingDocument增加了一个强制元素“sender_id”;

The task was to create a converter that based on json data and information from KnownTypeAttribute will be able to determine the most appropriate class that allows you to save the largest amount of information received. It should also be taken into account that json data may not have optional elements. To reduce the number of comparisons of json elements and properties of data models, I decided not to take into account the properties of the base class and to correlate with json elements only the properties of the inheritor classes.

服务数据:

{
    "documents": [
        {
            "document_id": "76b7be75-f4dc-44cd-90d2-0d1959922852",
            "date": "2019-12-10 11:32:49",
            "processed_date": "2019-12-10 11:32:49",
            "sender_id": "9dedee17-e43a-47f1-910e-3a88ff6bc258",
        },
        {
            "document_id": "5044a9ac-0314-4e9a-9e0c-817531120753",
            "date": "2019-12-10 11:32:44",
            "processed_date": "2019-12-10 11:32:44",
        }
    ], 
    "total": 2
}

数据模型:

/// <summary>
/// Service response model
/// </summary>
public class DocumentsRequestIdResponse
{
    [JsonProperty("documents")]
    public Document[] Documents { get; set; }

    [JsonProperty("total")]
    public int Total { get; set; }
}

// <summary>
/// Base document
/// </summary>
[JsonConverter(typeof(KnownTypeConverter))]
[KnownType(typeof(OutgoingDocument))]
[KnownType(typeof(IncomingDocument))]
public class Document
{
    [JsonProperty("document_id")]
    public Guid DocumentId { get; set; }

    [JsonProperty("date")]
    public DateTime Date { get; set; }

    [JsonProperty("processed_date")]
    public DateTime ProcessedDate { get; set; } 
}

/// <summary>
/// Outgoing document
/// </summary>
public class OutgoingDocument : Document
{
    // this property is optional and may not be present in the service's json response
    [JsonProperty("device_id")]
    public string DeviceId { get; set; }

    // this property is optional and may not be present in the service's json response
    [JsonProperty("msg_id")]
    public string MsgId { get; set; }
}

/// <summary>
/// Incoming document
/// </summary>
public class IncomingDocument : Document
{
    // this property is mandatory and is always populated by the service
    [JsonProperty("sender_sys_id")]
    public Guid SenderSysId { get; set; }
}

转换器:

public class KnownTypeConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return System.Attribute.GetCustomAttributes(objectType).Any(v => v is KnownTypeAttribute);
    }

    public override bool CanWrite => false;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // load the object 
        JObject jObject = JObject.Load(reader);

        // take custom attributes on the type
        Attribute[] attrs = Attribute.GetCustomAttributes(objectType);

        Type mostSuitableType = null;
        int countOfMaxMatchingProperties = -1;

        // take the names of elements from json data
        HashSet<string> jObjectKeys = GetKeys(jObject);

        // take the properties of the parent class (in our case, from the Document class, which is specified in DocumentsRequestIdResponse)
        HashSet<string> objectTypeProps = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Select(p => p.Name)
            .ToHashSet();

        // trying to find the right "KnownType"
        foreach (var attr in attrs.OfType<KnownTypeAttribute>())
        {
            Type knownType = attr.Type;
            if(!objectType.IsAssignableFrom(knownType))
                continue;

            // select properties of the inheritor, except properties from the parent class and properties with "ignore" attributes (in our case JsonIgnoreAttribute and XmlIgnoreAttribute)
            var notIgnoreProps = knownType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(p => !objectTypeProps.Contains(p.Name)
                            && p.CustomAttributes.All(a => a.AttributeType != typeof(JsonIgnoreAttribute) && a.AttributeType != typeof(System.Xml.Serialization.XmlIgnoreAttribute)));

            //  get serializable property names
            var jsonNameFields = notIgnoreProps.Select(prop =>
            {
                string jsonFieldName = null;
                CustomAttributeData jsonPropertyAttribute = prop.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(JsonPropertyAttribute));
                if (jsonPropertyAttribute != null)
                {
                    // take the name of the json element from the attribute constructor
                    CustomAttributeTypedArgument argument = jsonPropertyAttribute.ConstructorArguments.FirstOrDefault();
                    if(argument != null && argument.ArgumentType == typeof(string) && !string.IsNullOrEmpty((string)argument.Value))
                        jsonFieldName = (string)argument.Value;
                }
                // otherwise, take the name of the property
                if (string.IsNullOrEmpty(jsonFieldName))
                {
                    jsonFieldName = prop.Name;
                }

                return jsonFieldName;
            });


            HashSet<string> jKnownTypeKeys = new HashSet<string>(jsonNameFields);

            // by intersecting the sets of names we determine the most suitable inheritor
            int count = jObjectKeys.Intersect(jKnownTypeKeys).Count();

            if (count == jKnownTypeKeys.Count)
            {
                mostSuitableType = knownType;
                break;
            }

            if (count > countOfMaxMatchingProperties)
            {
                countOfMaxMatchingProperties = count;
                mostSuitableType = knownType;
            }
        }

        if (mostSuitableType != null)
        {
            object target = Activator.CreateInstance(mostSuitableType);
            using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
            {
                serializer.Populate(jObjectReader, target);
            }
            return target;
        }

        throw new SerializationException($"Could not serialize to KnownTypes and assign to base class {objectType} reference");
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    private HashSet<string> GetKeys(JObject obj)
    {
        return new HashSet<string>(((IEnumerable<KeyValuePair<string, JToken>>) obj).Select(k => k.Key));
    }

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

PS:在我的例子中,如果转换器没有选择任何一个继承程序(如果JSON数据只包含来自基类的信息,或者JSON数据不包含来自OutgoingDocument的可选元素,就会发生这种情况),那么将创建OutgoingDocument类的对象,因为它在KnownTypeAttribute属性列表中列在第一个。在这种情况下,您可以根据自己的要求改变KnownTypeConverter的实现。

其他回答

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

这使用了一种类似于Juval Lowy用于WCF的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());

只是想分享解决方案还在此基础上,与已知类型属性使用反射,不得不从任何基类派生类,解决方案可以受益于递归找到最佳匹配类虽然我不需要在我的例子中,匹配是由类型的转换器,如果已知类型将扫描所有直到匹配类型,所有属性在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();
        }
    }

下面是另一个解决方案,它避免使用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);
    }
}

使用标准的CustomCreationConverter,我在努力工作如何生成正确的类型(Person或Employee),因为为了确定这一点,你需要分析JSON,并且没有内置的方法来使用Create方法。

我找到了一个关于类型转换的讨论线程,它提供了答案。这里是一个链接:类型转换(存档链接)。

所需要的是继承JsonConverter的子类,覆盖ReadJson方法并创建一个新的抽象Create方法,该方法接受一个JObject。

JObject类提供了加载JSON对象和的方法 提供对此对象内数据的访问。

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

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

例子

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<T>的解决方案在互联网上到处都是,但有一个缺陷,在很少的情况下会表现出来。在ReadJson方法中创建的新JsonReader不继承任何原始阅读器的配置值(区域性,DateParseHandling, DateTimeZoneHandling, FloatParseHandling等…)在serializer.Populate()中使用新的JsonReader之前,应该复制这些值。

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

更新:我将此更新为一个更显式的方法,用于复制现有的读取器。这只是封装了复制单个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="jToken">The jToken to create a new reader for.</param>
/// <returns>The new disposable reader.</returns>
public static JsonReader CopyReaderForObject(JsonReader reader, JToken jToken)
{
    JsonReader jTokenReader = jToken.CreateReader();
    jTokenReader.Culture = reader.Culture;
    jTokenReader.DateFormatString = reader.DateFormatString;
    jTokenReader.DateParseHandling = reader.DateParseHandling;
    jTokenReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
    jTokenReader.FloatParseHandling = reader.FloatParseHandling;
    jTokenReader.MaxDepth = reader.MaxDepth;
    jTokenReader.SupportMultipleContent = reader.SupportMultipleContent;
    return jTokenReader;
}

应该这样使用:

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

旧的解决方案如下:

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