利用图腾和赞格纳的思想,我创建了一个 KnownTypeConverter
,它能够确定最适合的继承类,同时考虑到 json 数据可能没有可选元素。
因此,服务发送一个包含文档数组(入站和出站)的 JSON 响应。文档有共同的元素和不同的元素。在这种情况下,与出站文档相关的元素是可选的,可能不存在。
出于这个原因,创建了一个基础类 Document
,其中包括一组公共属性。还创建了两个继承类:
- OutgoingDocument
添加了两个可选元素 "device_id"
和 "msg_id"
;
- IncomingDocument
添加了一个必填元素 "sender_id"
;
任务是创建一个转换器,根据 json 数据和 KnownTypeAttribute 的信息,能够确定最适合的类,从而允许保存接收到的最大数量的信息。还应考虑到 json 数据可能没有可选元素。
为了减少比较 json 元素和数据模型属性的数量,决定不考虑基础类的属性,并仅关联继承者类的属性与 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
}
Data models:
public class DocumentsRequestIdResponse
{
[JsonProperty("documents")]
public Document[] Documents { get; set; }
[JsonProperty("total")]
public int Total { get; set; }
}
[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; }
}
public class OutgoingDocument : Document
{
[JsonProperty("device_id")]
public string DeviceId { get; set; }
[JsonProperty("msg_id")]
public string MsgId { get; set; }
}
public class IncomingDocument : Document
{
[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)
{
JObject jObject = JObject.Load(reader);
Attribute[] attrs = Attribute.GetCustomAttributes(objectType);
Type mostSuitableType = null;
int countOfMaxMatchingProperties = -1;
HashSet<string> jObjectKeys = GetKeys(jObject);
HashSet<string> objectTypeProps = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(p => p.Name)
.ToHashSet();
foreach (var attr in attrs.OfType<KnownTypeAttribute>())
{
Type knownType = attr.Type;
if(!objectType.IsAssignableFrom(knownType))
continue;
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)));
var jsonNameFields = notIgnoreProps.Select(prop =>
{
string jsonFieldName = null;
CustomAttributeData jsonPropertyAttribute = prop.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(JsonPropertyAttribute));
if (jsonPropertyAttribute != null)
{
CustomAttributeTypedArgument argument = jsonPropertyAttribute.ConstructorArguments.FirstOrDefault();
if(argument != null && argument.ArgumentType == typeof(string) && !string.IsNullOrEmpty((string)argument.Value))
jsonFieldName = (string)argument.Value;
}
if (string.IsNullOrEmpty(jsonFieldName))
{
jsonFieldName = prop.Name;
}
return jsonFieldName;
});
HashSet<string> jKnownTypeKeys = new HashSet<string>(jsonNameFields);
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
的实现方式。