Làm cách nào để triển khai JsonConverter tùy chỉnh trong JSON.NET để giải tuần tự hóa Danh sách các đối tượng lớp cơ sở?


301

Tôi đang cố gắng mở rộng ví dụ JSON.net được đưa ra ở đây http://james.newtonking.com/projects/json/help/CustomCreationConverter.html

Tôi có một lớp con khác xuất phát từ lớp cơ sở / Giao diện

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

Làm cách nào để tôi giải trừ theo Json trở lại Danh sách <Person>

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

Tôi không muốn sử dụng TypeNameHandling JsonSerializerS Settings. Tôi đặc biệt tìm kiếm triển khai JsonConverter tùy chỉnh để xử lý việc này. Các tài liệu và ví dụ xung quanh điều này là khá thưa thớt trên mạng. Tôi dường như không thể thực hiện phương thức ReadJson () bị ghi đè trong JsonConverter.


Câu trả lời:


315

Sử dụng tiêu chuẩn CustomCreationConverter, tôi đã phải vật lộn để tạo ra loại ( Personhoặc Employee) chính xác , bởi vì để xác định điều này, bạn cần phân tích JSON và không có cách nào được thực hiện bằng cách sử dụng Createphương thức này.

Tôi tìm thấy một chủ đề thảo luận liên quan đến chuyển đổi loại và hóa ra nó cung cấp câu trả lời. Đây là một liên kết: Loại chuyển đổi .

Điều cần thiết là phân lớp JsonConverter, ghi đè ReadJsonphương thức và tạo Createphương thức trừu tượng mới chấp nhận a JObject.

Lớp JObject cung cấp một phương tiện để tải một đối tượng JSON và cung cấp quyền truy cập vào dữ liệu trong đối tượng này.

ReadJsonPhương thức được ghi đè tạo một JObjectvà gọi Createphương thức (được thực hiện bởi lớp trình chuyển đổi dẫn xuất của chúng ta), truyền vào JObjectthể hiện.

Trường JObjecthợp này sau đó có thể được phân tích để xác định loại chính xác bằng cách kiểm tra sự tồn tại của các trường nhất định.

Thí dụ

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

6
Sẽ rất tốt nếu phương thức WriteJson được triển khai và cung cấp một số phương thức trừu tượng để xâu chuỗi kiểu này.
Triynko

54
LƯU Ý: Giải pháp này có trên internet, nhưng có một lỗ hổng tự thể hiện trong những dịp hiếm hoi. Mới JsonReaderđược tạo ra trong ReadJsonphương pháp không thừa hưởng bất kỳ giá trị cấu hình người đọc gốc ( Culture, DateParseHandling, DateTimeZoneHandling, FloatParseHandling, vv ...). Những giá trị này nên được sao chép trước khi sử dụng mới JsonReadertrong serializer.Populate().
Alain

9
Để ngăn việc tạo JsonReader mới (do các lý do được đề cập bởi @Alain) hoặc nếu bạn cần quyết định về loại đối tượng được tạo dựa trên một số giá trị của cha mẹ, hãy xem giải pháp này stackoverflow.com/a/22539430/1038496 . Có vẻ hiệu quả hơn và rõ ràng hơn đối với tôi (ngay cả đối với loại vấn đề này).
Zoka

8
@Triynko: Sau một thời gian dài tìm kiếm, tôi phát hiện ra rằng lớp JsonConvertercó một thuộc tính được gọi CanReadCanWrite. Nếu bạn không cần WriteJsontriển khai tùy chỉnh , nó đủ để CanWritetrả lại FALSE. Hệ thống sau đó sẽ quay trở lại hành vi mặc định. @jdavies: Vui lòng thêm nó vào câu trả lời của bạn. Nếu không nó sẽ sụp đổ trên serialization.
SimonSimCity

1
Tôi thấy rằng bạn phải xử lý các trường hợp NULL, nếu không sẽ xảy ra lỗi. Sử dụng: ||| if (reader.TokenType == JsonToken.Null) trả về null; | | nguồn: stackoverflow.com/a 432185296/857291
Cesar

96

Các giải pháp trên cho JsonCreationConverter<T>tất cả là trên internet, nhưng có một lỗ hổng tự thể hiện trong những dịp hiếm hoi. JsonReader mới được tạo trong phương thức ReadJson không kế thừa bất kỳ giá trị cấu hình nào của trình đọc ban đầu (Culture, DatePudeHandling, DateTimeZoneHandling, FloatPudeHandling, v.v ...). Các giá trị này phải được sao chép trước khi sử dụng JsonReader mới trong serializer.Population ().

Đây là cách tốt nhất tôi có thể đưa ra để khắc phục một số vấn đề với việc thực hiện ở trên, nhưng tôi vẫn nghĩ rằng có một số điều bị bỏ qua:

Cập nhật Tôi đã cập nhật điều này để có một phương pháp rõ ràng hơn tạo ra một bản sao của một trình đọc hiện có. Điều này chỉ gói gọn quá trình sao chép trên các cài đặt JsonReader riêng lẻ. Lý tưởng nhất là chức năng này sẽ được duy trì trong chính thư viện Newtonsoft, nhưng bây giờ, bạn có thể sử dụng như sau:

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

Điều này nên được sử dụng như sau:

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

Giải pháp cũ hơn sau:

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

7
Đừng quên suy nghĩ về CanWrite! (Tôi đặt thành sai) Bạn có thể kết thúc bằng selfreferencesloops (Tôi đã làm). stackoverflow.com/questions/12314438/ từ
Dribbel

1
Bạn cũng không cần phải thực hiện WriteJson? Làm thế nào để trình chuyển đổi biết làm thế nào để chuyển đổi từ đối tượng sang json?
David S.

15

Chỉ cần nghĩ rằng tôi sẽ chia sẻ một giải pháp cũng dựa trên giải pháp này hoạt động với thuộc tính Knftimeype bằng cách sử dụng phản chiếu, phải lấy lớp dẫn xuất từ ​​bất kỳ lớp cơ sở nào, giải pháp có thể có lợi từ đệ quy để tìm lớp phù hợp nhất mặc dù tôi không cần nó trong trường hợp, việc so khớp được thực hiện theo loại được cung cấp cho trình chuyển đổi nếu nó có KnownTypes, nó sẽ quét tất cả chúng cho đến khi nó khớp với một loại có tất cả các thuộc tính bên trong chuỗi json, lần đầu tiên khớp sẽ được chọn.

cách sử dụng đơn giản như:

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

trong trường hợp trên, ret sẽ thuộc loại B.

Các lớp JSON:

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

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

Mã chuyển đổi:

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

1
Tôi thực sự thích giải pháp này, nhưng tôi có thể thấy một vấn đề khi có nhiều loại đã biết có cùng tên thuộc tính. Bạn đã gặp phải vấn đề đó? Cám ơn.
covo

8

Dự án JsonSubTypes thực hiện một trình chuyển đổi chung xử lý tính năng này với sự trợ giúp của các thuộc tính.

Đối với mẫu bê tông được cung cấp ở đây là cách nó hoạt động:

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

2
Chuyển đổi rất hữu ích. Chỉ cần tiết kiệm cho tôi hàng giờ mã hóa một công cụ chuyển đổi!
Carlos Rodriguez

7

Đây là một bản mở rộng cho câu trả lời của totem. Về cơ bản, nó thực hiện điều tương tự nhưng đối sánh thuộc tính dựa trên đối tượng json được tuần tự hóa, không phản ánh đối tượng .net. Điều này rất quan trọng nếu bạn đang sử dụng [JsonProperty], sử dụng CamelCasePropertyNamesContractResolver hoặc làm bất cứ điều gì khác sẽ khiến json không khớp với đối tượng .net.

Cách sử dụng rất đơn giản:

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

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

Mã chuyển đổi:

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

5

Là một biến thể khác của giải pháp loại đã biết của Totem, bạn có thể sử dụng sự phản chiếu để tạo một trình phân giải loại chung để tránh sự cần thiết phải sử dụng các thuộc tính loại đã biết.

Điều này sử dụng một kỹ thuật tương tự như GenericResolver của Juval Low dành cho WCF.

Miễn là lớp cơ sở của bạn là trừu tượng hoặc giao diện, các loại đã biết sẽ được xác định tự động thay vì phải được trang trí với các thuộc tính loại đã biết.

Trong trường hợp của riêng tôi, tôi đã chọn sử dụng thuộc tính $ type để chỉ định loại trong đối tượng json của mình thay vì cố gắng xác định nó từ các thuộc tính, mặc dù bạn có thể mượn từ các giải pháp khác ở đây để sử dụng xác định dựa trên thuộc tính.

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

Sau đó, nó có thể được cài đặt như một định dạng

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

1

Đây là một giải pháp khác tránh sử dụng jObject.CreateReader()và thay vào đó tạo một cái mới JsonTextReader(đó là hành vi được sử dụng bởi JsonCreate.Deserialzephương thức mặc định :

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

1

Rất nhiều lần việc thực hiện sẽ tồn tại trong cùng một không gian tên với giao diện. Vì vậy, tôi đã đưa ra điều này:

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

Do đó, bạn có thể bao gồm điều này trên toàn cầu như vậy:

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

1

Sử dụng ý tưởng của totemzlangner , tôi đã tạo ra một KnownTypeConverterthứ sẽ có thể xác định người thừa kế phù hợp nhất, trong khi tính đến việc dữ liệu json có thể không có các yếu tố tùy chọn.

Vì vậy, dịch vụ sẽ gửi một phản hồi JSON có chứa một loạt các tài liệu (đến và đi). Tài liệu có cả một tập hợp các yếu tố chung và các yếu tố khác nhau. Trong trường hợp này, các yếu tố liên quan đến các tài liệu gửi đi là tùy chọn và có thể vắng mặt.

Về vấn đề này, một lớp cơ sở Documentđã được tạo ra bao gồm một tập các thuộc tính chung. Hai lớp kế thừa cũng được tạo: - OutgoingDocumentthêm hai phần tử tùy chọn "device_id""msg_id"; - IncomingDocumentthêm một yếu tố bắt buộc "sender_id";

Nhiệm vụ là tạo ra một trình chuyển đổi dựa trên dữ liệu và thông tin json từ KnownTypeAttribution sẽ có thể xác định lớp thích hợp nhất cho phép bạn lưu lượng thông tin lớn nhất nhận được. Cũng cần lưu ý rằng dữ liệu json có thể không có các yếu tố tùy chọn. Để giảm số lượng so sánh các phần tử json và các thuộc tính của các mô hình dữ liệu, tôi quyết định không tính đến các thuộc tính của lớp cơ sở và chỉ tương quan với các phần tử json các thuộc tính của các lớp kế thừa.

Dữ liệu từ dịch vụ:

{
    "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
}

Mô hình dữ liệu:

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

Chuyển đổi:

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: Trong trường hợp của tôi, nếu không có người thừa kế nào không được chọn bởi trình chuyển đổi (điều này có thể xảy ra nếu dữ liệu JSON chỉ chứa thông tin từ lớp cơ sở hoặc dữ liệu JSON không chứa các phần tử tùy chọn từ OutgoingDocument), thì một đối tượng của OutgoingDocumentlớp sẽ được tạo, vì nó được liệt kê đầu tiên trong danh sách các KnownTypeAttributethuộc tính. Theo yêu cầu của bạn, bạn có thể thay đổi việc thực hiện KnownTypeConvertertrong tình huống này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.