使用Json.NET反序列化接口时如何自动选择具体类型
解决问题的最简单方法是使用序列化和反序列化JSON(在客户端和服务器端)
TypeNameHandling =TypeNameHandling.Auto。如果这样做,您的JSON将包括为
IFIeld属性序列化的实际类型,如下所示:
{ "CurrentField": { "$type": "MyNamespace.Field2, MyAssembly", "Name": "name", "MyStringValue": "my string value" }}
但是,请注意Newtonsoft文档中的这一警告:
当您的应用程序从外部源反序列化JSON时,应谨慎使用TypeNameHandling。反序列化除None以外的其他值时,应使用自定义SerializationBinder验证传入的类型。
有关为什么这样做的必要性的讨论,请参阅Newtonsoft
Json中的TypeNameHandling警告,如何配置Json.NET以创建易受攻击的Web
API,以及AlvaroMuñoz和Oleksandr
Mirosh的blackhat论文https://www.blackhat.com/docs/我们17 /周四/us-17-Munoz-Friday-
The-13th-JSON-Attacks-
wp.pdf
如果出于某种原因无法更改服务器输出,则可以创建一个
JsonConverter将JSON加载到中
JObject并检查实际存在的字段,然后搜索可能的具体类型以找到具有相同属性的字段:
public class JsonDerivedTypeConverer<T> : JsonConverter{ public JsonDerivedTypeConverer() { } public JsonDerivedTypeConverer(params Type[] types) { this.DerivedTypes = types; } readonly HashSet<Type> derivedTypes = new HashSet<Type>(); public IEnumerable<Type> DerivedTypes { get { return derivedTypes.ToArray(); } set { if (value == null) throw new ArgumentNullException(); derivedTypes.Clear(); if (value != null) derivedTypes.UnionWith(value); } } JsonObjectContract FindContract(JObject obj, JsonSerializer serializer) { List<JsonObjectContract> bestContracts = new List<JsonObjectContract>(); foreach (var type in derivedTypes) { if (type.IsAbstract) continue; var contract = serializer.ContractResolver.ResolveContract(type) as JsonObjectContract; if (contract == null) continue; if (obj.Properties().Select(p => p.Name).Any(n => contract.Properties.GetClosestMatchProperty(n) == null)) continue; if (bestContracts.Count == 0 || bestContracts[0].Properties.Count > contract.Properties.Count) { bestContracts.Clear(); bestContracts.Add(contract); } else if (contract.Properties.Count == bestContracts[0].Properties.Count) { bestContracts.Add(contract); } } return bestContracts.Single(); } public override bool CanConvert(Type objectType) { return objectType == typeof(T); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; var obj = JObject.Load(reader); // Throws an exception if the current token is not an object. var contract = FindContract(obj, serializer); if (contract == null) throw new JsonSerializationException("no contract found for " + obj.ToString()); if (existingValue == null || !contract.UnderlyingType.IsAssignableFrom(existingValue.GetType())) existingValue = contract.DefaultCreator(); using (var sr = obj.CreateReader()) { serializer.Populate(sr, existingValue); } return existingValue; } public override bool CanWrite { get { return false; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }}
然后,您可以将其用作转换器
IField:
[JsonConverter(typeof(JsonDerivedTypeConverer<IField>), new object [] { new Type [] { typeof(Field1), typeof(Field2) } })]public interface IField{ string Name { get; set; }}
请注意,此解决方案有些脆弱。如果服务器省略
MyStringValueor
MyValue字段(
DefaultValueHandling =DefaultValueHandling.Ignore例如,因为它们具有默认值和),则转换器将不知道要创建哪种类型,并且将引发异常。同样,如果两个实现的具体类型
IField具有相同的属性名称,但类型不同,则转换器将引发异常。使用
TypeNameHandling.Auto可以避免这些潜在的问题。
更新资料
以下版本检查该
"$type"参数是否存在,如果
TypeNameHandling !=TypeNameHandling.None,则返回默认序列化。它必须采取一些技巧来防止回退时无限递归:
public class JsonDerivedTypeConverer<T> : JsonConverter{ public JsonDerivedTypeConverer() { } public JsonDerivedTypeConverer(params Type[] types) { this.DerivedTypes = types; } readonly HashSet<Type> derivedTypes = new HashSet<Type>(); public IEnumerable<Type> DerivedTypes { get { return derivedTypes.ToArray(); } set { derivedTypes.Clear(); if (value != null) derivedTypes.UnionWith(value); } } JsonObjectContract FindContract(JObject obj, JsonSerializer serializer) { List<JsonObjectContract> bestContracts = new List<JsonObjectContract>(); foreach (var type in derivedTypes) { if (type.IsAbstract) continue; var contract = serializer.ContractResolver.ResolveContract(type) as JsonObjectContract; if (contract == null) continue; if (obj.Properties().Select(p => p.Name).Where(n => n != "$type").Any(n => contract.Properties.GetClosestMatchProperty(n) == null)) continue; if (bestContracts.Count == 0 || bestContracts[0].Properties.Count > contract.Properties.Count) { bestContracts.Clear(); bestContracts.Add(contract); } else if (contract.Properties.Count == bestContracts[0].Properties.Count) { bestContracts.Add(contract); } } return bestContracts.Single(); } public override bool CanConvert(Type objectType) { return objectType == typeof(T); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; var obj = JObject.Load(reader); // Throws an exception if the current token is not an object. if (obj["$type"] != null && serializer.TypeNameHandling != TypeNameHandling.None) { // Prevent infinite recursion when using an explicit converter in the list. var removed = serializer.Converters.Remove(this); try { // Kludge to prevent infinite recursion when using JsonConverterAttribute on the type: deserialize to object. return obj.ToObject(typeof(object), serializer); } finally { if (removed) serializer.Converters.Add(this); } } else { var contract = FindContract(obj, serializer); if (contract == null) throw new JsonSerializationException("no contract found for " + obj.ToString()); if (existingValue == null || !contract.UnderlyingType.IsAssignableFrom(existingValue.GetType())) existingValue = contract.DefaultCreator(); using (var sr = obj.CreateReader()) { serializer.Populate(sr, existingValue); } return existingValue; } } public override bool CanWrite { get { return false; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)