带接口的JsonConverter
我有一个来自客户端的对象,并自动从Web Api 2反序列化。
现在我的模型的一个属性有问题。 此属性“CurrentField”属于IField类型,此接口有2种不同的实现。
这是我的模特(只是假人)
public class MyTest { public IField CurrentField {get;set;} } public interface IField{ string Name {get;set;} } public Field1 : IField{ public string Name {get;set;} public int MyValue {get;set;} } public Field2 : IField{ public string Name {get;set;} public string MyStringValue {get;set;} }
我试图创建一个自定义JsonConverter来找出我的客户端的对象是什么类型(Field1或Field2),但我只是不知道如何。
调用我的转换器,当我调用var obj = JObject.load(reader)时,我可以看到该对象;
但我怎样才能知道它是什么类型的? 我做不了类似的事情
if(obj is Field1) ...
这是我应该检查这个的方法吗?
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
如何在使用Json.NET反序列化接口时自动选择具体类型
解决问题的最简单方法是使用TypeNameHandling = TypeNameHandling.Auto
序列化和反序列化您的JSON(在客户端和服务器端)。 如果这样做,您的JSON将包含为IFIeld
属性序列化的实际类型,如下所示:
{ "CurrentField": { "$type": "MyNamespace.Field2, MyAssembly", "Name": "name", "MyStringValue": "my string value" } }
但是,请注意Newtonsoft文档中的这一注意事项:
当您的应用程序从外部源反序列化JSON时,应谨慎使用TypeNameHandling。 使用非None以外的值进行反序列化时,应使用自定义SerializationBindervalidation传入类型。
有关为什么需要这样做的讨论,请参阅Newtonsoft Json中的TypeNameHandling警告 , 如何配置Json.NET以创建易受攻击的Web API ,以及AlvaroMuñoz和Oleksandr Mirosh的黑帽纸https://www.blackhat.com/docs/ US-17 /周四/ US-17-穆尼奥斯-周五最的13 JSON-攻击,wp.pdf
如果由于某种原因您无法更改服务器输出的内容,您可以创建一个JsonConverter
,将JSON加载到JObject
并检查实际存在的字段,然后搜索可能的具体类型以查找具有相同属性的字段:
public class JsonDerivedTypeConverer : JsonConverter { public JsonDerivedTypeConverer() { } public JsonDerivedTypeConverer(params Type[] types) { this.DerivedTypes = types; } readonly HashSet derivedTypes = new HashSet (); public IEnumerable 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 bestContracts = new List (); 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), new object [] { new Type [] { typeof(Field1), typeof(Field2) } })] public interface IField { string Name { get; set; } }
请注意,此解决方案有点脆弱。 如果服务器省略了MyStringValue
或MyValue
字段(例如,因为它们具有默认值而DefaultValueHandling = DefaultValueHandling.Ignore
),则转换器将不知道要创建哪个类型并将引发exception。 类似地,如果实现IField
两个具体类型具有相同的属性名称,仅在类型上有所不同,则转换器将抛出exception。 使用TypeNameHandling.Auto
可以避免这些潜在的问题。
更新
以下版本检查是否存在"$type"
参数,如果TypeNameHandling != TypeNameHandling.None
,则返回默认序列化。 它必须做一些技巧,以防止在回落时无限递归:
public class JsonDerivedTypeConverer : JsonConverter { public JsonDerivedTypeConverer() { } public JsonDerivedTypeConverer(params Type[] types) { this.DerivedTypes = types; } readonly HashSet derivedTypes = new HashSet (); public IEnumerable DerivedTypes { get { return derivedTypes.ToArray(); } set { derivedTypes.Clear(); if (value != null) derivedTypes.UnionWith(value); } } JsonObjectContract FindContract(JObject obj, JsonSerializer serializer) { List bestContracts = new List (); 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(); } }
- 使用URL在Web Api 2中进行控制器版本控制
- 如何防止单个对象属性在字符串时转换为DateTime
- C# – 无法调用HttpConfiguration扩展方法
- 使用ASP Identity 2 POST到/ Token端点时始终收到’invalid_client’错误
- Web API 2 Http Post方法
- IHttpActionResult vs async Task
- WebAPi – 统一来自ApiController和OAuthAuthorizationServerProvider的错误消息格式
- 使用JwtAuthForWebAPI的Web APi2身份validation不接受JWT令牌
- Web API Controller将MemoryStream转换为StreamContent