टोटेम और ज़्लेंग्नेर के विचार का उपयोग करते हुए , मैंने एक KnownTypeConverter
ऐसा बनाया है जो सबसे उपयुक्त उत्तराधिकारी को निर्धारित करने में सक्षम होगा, जबकि यह ध्यान में रखते हुए कि json डेटा में वैकल्पिक तत्व नहीं हो सकते हैं।
इसलिए, सेवा एक JSON प्रतिक्रिया भेजता है जिसमें दस्तावेज़ों की एक सरणी होती है (आवक और जावक)। दस्तावेजों में तत्वों और अलग-अलग दोनों का एक सामान्य सेट है। इस मामले में, निवर्तमान दस्तावेजों से संबंधित तत्व वैकल्पिक हैं और अनुपस्थित हो सकते हैं।
इस संबंध में, एक आधार वर्ग Document
बनाया गया था जिसमें संपत्तियों का एक सामान्य समूह शामिल है। दो अंतर्निहित वर्ग भी बनाए जाते हैं: - OutgoingDocument
दो वैकल्पिक तत्वों को जोड़ता है "device_id"
और "msg_id"
; - IncomingDocument
एक अनिवार्य तत्व जोड़ता है "sender_id"
;
कार्य एक कनवर्टर बनाने के लिए था जो कि नॉटआउट टाइप से डेटा और जानकारी के आधार पर, सबसे उपयुक्त वर्ग को निर्धारित करने में सक्षम होगा जो आपको प्राप्त जानकारी की सबसे बड़ी राशि को बचाने की अनुमति देता है। यह भी ध्यान में रखा जाना चाहिए कि json डेटा में वैकल्पिक तत्व नहीं हो सकते हैं। जोंस तत्वों और डेटा मॉडल के गुणों की तुलना को कम करने के लिए, मैंने बेस क्लास के गुणों को ध्यान में नहीं रखने का फैसला किया है और केवल जीनस तत्वों के साथ संबंध स्थापित किया है।
सेवा से डेटा:
{
"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;
}
}
पुनश्च: मेरे मामले में, अगर कोई भी इनहेरिटर कन्वर्टर द्वारा नहीं चुना गया है (यह तब हो सकता है यदि JSON डेटा में केवल बेस क्लास से जानकारी हो या JSON डेटा में वैकल्पिक तत्व शामिल न हों OutgoingDocument
), तो OutgoingDocument
क्लास का एक ऑब्जेक्ट बनाया जाएगा, क्योंकि यह पहले KnownTypeAttribute
विशेषताओं की सूची में सूचीबद्ध है । आपके अनुरोध पर, आप KnownTypeConverter
इस स्थिति में कार्यान्वयन को भिन्न कर सकते हैं ।