JsonConverter与接口 [英] JsonConverter with Interface

查看:119
本文介绍了JsonConverter与接口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个来自于客户端,并从网页API 2得到反序列化对象自动。

现在我有我的模型的一个性质的问题。此属性CurrentField是类型IField,并有2个不同的此接口的实现。

这是我的模型(只是一个虚拟的)

 公共类MyTest的
{
    公共IField CurrentField {获取;设置;}
}公共接口IField {
    字符串名称{;设置;}
}公共字段1:IField {
    公共字符串名称{;设置;}
    公众诠释myvalue的{获取;设置;}
}公共字段2:IField {
    公共字符串名称{;设置;}
    公共字符串MyStringValue {获取;设置;}
}

我试图创建一个自定义JsonConverter找出什么类型从客户端(场1或场),我的对象,但我不知道怎么办。

我的转换器被调用,我可以看到的对象时,我打电话
VAR OBJ = JObject.load(读卡器);

但我怎么能找出它是什么类型?我不能做这样的事情。

 如果(obj是字段1)...

这是我应该检查这个权利的方法?

 公众覆盖对象ReadJson(JsonReader读者,类型的objectType,对象existingValue,JsonSerializer串行)


解决方案

如何反序列化使用Json.NET的接口时,会自动选择一个具体类型

解决你的问题,最简单的方法是使用的 TypeNameHandling = TypeNameHandling.Auto 。如果你这样做,你的JSON将包括实际类型序列化的 IFIeld 财产,像这样:


  {
  CurrentField:{
    $类型:MyNamespace.Field2,MyAssembly程序,
    名称:名字,
    MyStringValue:我的字符串值
  }
}


如果出于某种原因,你不能改变什么服务器输出,你可以创建一个加载JSON成 JObject JsonConverter $ C>和检查,看看哪些领域实际上是present,然后通过可能的具体类型的搜索找到一个具有相同属性:

 公共类JsonDerivedTypeConverer< T> :JsonConverter
{
    公共JsonDerivedTypeConverer(){}    公共JsonDerivedTypeConverer(PARAMS键入[]类型)
    {
        this.DerivedTypes =类型;
    }    只读的HashSet<类型> derivedTypes =新的HashSet<类型和GT;();    公共IEnumerable的<类型> DerivedTypes
    {
        得到
        {
            返回derivedTypes.ToArray();
        }
        组
        {
            如果(价值== NULL)
                抛出新的ArgumentNullException();
            derivedTypes.Clear();
            如果(值!= NULL)
                derivedTypes.UnionWith(值);
        }
    }    JsonObjectContract FindContract(JObject OBJ,JsonSerializer串行)
    {
        清单< JsonObjectContract> bestContracts =新的List< JsonObjectContract>();
        的foreach(在derivedTypes VAR型)
        {
            如果(type.IsAbstract)
                继续;
            VAR合同= serializer.ContractResolver.ResolveContract(类型)为JsonObjectContract;
            如果(合同== NULL)
                继续;
            如果(obj.Properties()选择(P =方式> p.Name)。任何(N => contract.Properties.GetClosestMatchProperty(N)== NULL))
                继续;
            如果(bestContracts.Count == 0 || bestContracts [0] .Properties.Count> contract.Properties.Count)
            {
                bestContracts.Clear();
                bestContracts.Add(合同);
            }
            否则如果(contract.Properties.Count == bestContracts [0] .Properties.Count)
            {
                bestContracts.Add(合同);
            }
        }
        返回bestContracts.Single();
    }    公众覆盖布尔CanConvert(类型的objectType)
    {
        返回的objectType == typeof运算(T);
    }    公众覆盖对象ReadJson(JsonReader读者,类型的objectType,对象existingValue,JsonSerializer串行)
    {
        如果(reader.TokenType == JsonToken.Null)
            返回null;
        VAR OBJ = JObject.Load(读卡器); //如果当前标记是不是一个对象抛出异常。
        VAR合同= FindContract(OBJ,序列化);
        如果(合同== NULL)
            抛出新JsonSerializationException(没有找到符合合同+ obj.ToString());
        如果(existingValue == NULL ||!contract.UnderlyingType.IsAssignableFrom(existingValue.GetType()))
            existingValue = contract.DefaultCreator();
        使用(VAR SR = obj.CreateReader())
        {
            serializer.Populate(SR,existingValue);
        }
        返回existingValue;
    }    公众覆盖布尔CanWrite {{返回FALSE; }}    公共覆盖无效WriteJson(JsonWriter作家,对象的值,JsonSerializer串行)
    {
        抛出新NotImplementedException();
    }
}

那么你可以申请,作为一个转换器,以 IField

  [JsonConverter(typeof运算(JsonDerivedTypeConverer< IField>),新的对象[] {新类型[] {typeof运算(字段1)的typeof(字段2)}})]
公共接口IField
{
    字符串名称{;组; }
}

请注意,此解决方案是有点脆弱。如果服务器忽略了 MyStringValue myvalue的字段(因为他们有默认值,的 DefaultValueHandling = DefaultValueHandling.Ignore ,例如),然后该转换器将不知道哪种类型的创建,并会抛出异常。同样,如果两个具体类型实施 IField 具有相同的属性名称,只有在不同类型的转换器将抛出异常。使用 TypeNameHandling.Auto 避免了这些潜在的问题。

更新

以下版本检查,看是否$键入参数是present,如果 TypeNameHandling!= TypeNameHandling.None ,倒在默认的序列化。它有做了几个招数prevent无限递归回落时:

 公共类JsonDerivedTypeConverer< T> :JsonConverter
{
    公共JsonDerivedTypeConverer(){}    公共JsonDerivedTypeConverer(PARAMS键入[]类型)
    {
        this.DerivedTypes =类型;
    }    只读的HashSet<类型> derivedTypes =新的HashSet<类型和GT;();    公共IEnumerable的<类型> DerivedTypes
    {
        得到
        {
            返回derivedTypes.ToArray();
        }
        组
        {
            derivedTypes.Clear();
            如果(值!= NULL)
                derivedTypes.UnionWith(值);
        }
    }    JsonObjectContract FindContract(JObject OBJ,JsonSerializer串行)
    {
        清单< JsonObjectContract> bestContracts =新的List< JsonObjectContract>();
        的foreach(在derivedTypes VAR型)
        {
            如果(type.IsAbstract)
                继续;
            VAR合同= serializer.ContractResolver.ResolveContract(类型)为JsonObjectContract;
            如果(合同== NULL)
                继续;
            如果(obj.Properties()选择(p值=方式> p.Name)。凡(N => N!=$型)在任何(N =方式> contract.Properties.GetClosestMatchProperty(正)==空值))
                继续;
            如果(bestContracts.Count == 0 || bestContracts [0] .Properties.Count> contract.Properties.Count)
            {
                bestContracts.Clear();
                bestContracts.Add(合同);
            }
            否则如果(contract.Properties.Count == bestContracts [0] .Properties.Count)
            {
                bestContracts.Add(合同);
            }
        }
        返回bestContracts.Single();
    }    公众覆盖布尔CanConvert(类型的objectType)
    {
        返回的objectType == typeof运算(T);
    }    公众覆盖对象ReadJson(JsonReader读者,类型的objectType,对象existingValue,JsonSerializer串行)
    {
        如果(reader.TokenType == JsonToken.Null)
            返回null;
        VAR OBJ = JObject.Load(读卡器); //如果当前标记是不是一个对象抛出异常。
        如果(OBJ [$型] = NULL&放大器;!&安培;!serializer.TypeNameHandling = TypeNameHandling.None)
        {
            //使用列表中的一个明确的转换程序时prevent无限递归。
            VAR删除= serializer.Converters.Remove(本);
            尝试
            {
                //对未完善的类型使用JsonConverterAttribute时prevent无限递归:反序列化对象。
                返回obj.ToObject(typeof运算(对象),序列化);
            }
            最后
            {
                如果(删除)
                    serializer.Converters.Add(本);
            }
        }
        其他
        {
            VAR合同= FindContract(OBJ,序列化);
            如果(合同== NULL)
                抛出新JsonSerializationException(没有找到符合合同+ obj.ToString());
            如果(existingValue == NULL ||!contract.UnderlyingType.IsAssignableFrom(existingValue.GetType()))
                existingValue = contract.DefaultCreator();
            使用(VAR SR = obj.CreateReader())
            {
                serializer.Populate(SR,existingValue);
            }
            返回existingValue;
        }
    }    公众覆盖布尔CanWrite {{返回FALSE; }}    公共覆盖无效WriteJson(JsonWriter作家,对象的值,JsonSerializer串行)
    {
        抛出新NotImplementedException();
    }
}

I have an object which comes from the client and get deserialized from the Web Api 2 automatically.

Now I have a problem with one property of my model. This property "CurrentField" is of Type IField and there are 2 different Implementations of this interface.

This is my model (just a dummy)

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;}
}

I tried to create a custom JsonConverter to find out of what type my object from the client is (Field1 or Field2) but I just don't know how.

My Converter gets called and I can see the object when I call var obj = JObject.load(reader);

but how can I find out what type it is? I can't do something like

if(obj is Field1) ...

this is the method where I should check this right?

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)

解决方案

How to automatically select a concrete type when deserializing an interface using Json.NET

The easiest way to solve your problem is to serialize and deserialize your JSON (on both the client and server sides) with TypeNameHandling = TypeNameHandling.Auto. If you do, your JSON will include the actual type serialized for an IFIeld property, like so:

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

If for whatever reason you cannot change what the server outputs, you can create a JsonConverter that loads the JSON into a JObject and checks to see what fields are actually present, then searches through possible concrete types to find one with the same properties:

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();
    }
}

Then you can apply that as a converter to IField:

[JsonConverter(typeof(JsonDerivedTypeConverer<IField>), new object [] { new Type [] { typeof(Field1), typeof(Field2) } })]
public interface IField
{
    string Name { get; set; }
}

Note that this solution is a little fragile. If the server omits the MyStringValue or MyValue fields (because they have default value and DefaultValueHandling = DefaultValueHandling.Ignore, for example) then the converter won't know which type to create and will throw an exception. Similarly, if two concrete types implementing IField have the same property names, differing only in type, the converter will throw an exception. Using TypeNameHandling.Auto avoids these potential problems.

Update

The following version checks to see if the "$type" parameter is present, and if TypeNameHandling != TypeNameHandling.None, falls back on default serialization. It has to do a couple of tricks to prevent infinite recursion when falling back:

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();
    }
}

这篇关于JsonConverter与接口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆