带接口的JsonConverter

时间:2015-10-24 18:18:36

标签: c# json asp.net-web-api2

我有一个来自客户端的对象,并自动从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)

1 个答案:

答案 0 :(得分:2)

如何在使用Json.NET反序列化接口时自动选择具体类型

解决问题的最简单方法是使用TypeNameHandling = TypeNameHandling.Auto序列化和反序列化您的JSON(在客户端和服务器端)。如果这样做,您的JSON将包含为IFIeld属性序列化的实际类型,如下所示:

{
  "CurrentField": {
    "$type": "MyNamespace.Field2, MyAssembly",
    "Name": "name",
    "MyStringValue": "my string value"
  }
}

但请注意Newtonsoft docs

中的这一注意事项
  

当您的应用程序从外部源反序列化JSON时,应谨慎使用TypeNameHandling。使用非None以外的值进行反序列化时,应使用自定义SerializationBinder验证传入类型。

有关为何需要这样做的讨论,请参阅TypeNameHandling caution in Newtonsoft JsonHow to configure Json.NET to create a vulnerable web API和AlvaroMuñoz& Oleksandr Mirosh的黑帽纸https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf

如果由于某种原因您无法更改服务器输出的内容,您可以创建一个{J}加载JSON到JsonConverter并检查实际存在的字段,然后搜索可能的具体内容找到具有相同属性的类型:

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; } } MyStringValue字段(例如,因为它们具有默认值和DefaultValueHandling = DefaultValueHandling.Ignore),则转换器将不知道要创建哪种类型并将引发异常。类似地,如果实现MyValue的两个具体类型具有相同的属性名称,仅在类型上有所不同,则转换器将抛出异常。使用IField可以避免这些潜在的问题。

<强>更新

以下版本检查TypeNameHandling.Auto参数是否存在,以及"$type"是否依赖于默认序列化。它必须做一些技巧来防止无法递归时退回:

TypeNameHandling != TypeNameHandling.None