Sử dụng ý tưởng của totem và zlangner , 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"và "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.