首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >带接口的JsonConverter

带接口的JsonConverter
EN

Stack Overflow用户
提问于 2015-10-24 18:18:35
回答 1查看 6.4K关注 0票数 7

我有一个来自客户端的对象,并自动从Web 2反序列化。

现在,我对我的模型的一个属性有了问题。这个属性"CurrentField“是IField类型的,这个接口有两个不同的实现。

这是我的模型(只是个假人)

代码语言:javascript
复制
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(读取器)时,我可以看到对象;

但是我怎么知道它是哪种类型的呢?我不能做像

代码语言:javascript
复制
if(obj is Field1) ...

这就是我应该检查的方法,对吗?

代码语言:javascript
复制
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2015-10-24 19:57:44

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

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

{ "CurrentField":{ "$type":"MyNamespace.Field2,MyAssembly","Name":"name","MyStringValue":“MyStringValue”}}

但是,请注意Newtonsoft文档发出的警告

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

关于为什么需要这样做的讨论,请参阅Newtonsoft Json中的TypeNameHandling警告如何配置Json.NET以创建易受攻击的web和Alvaro Mu oz& Oleksandr Mirosh的黑帽纸https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf

如果由于任何原因无法更改服务器输出的内容,则可以创建一个JsonConverter,将JSON加载到JObject中,并检查实际存在哪些字段,然后搜索可能的具体类型,以找到具有相同属性的类型:

代码语言:javascript
复制
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

代码语言:javascript
复制
[JsonConverter(typeof(JsonDerivedTypeConverer<IField>), new object [] { new Type [] { typeof(Field1), typeof(Field2) } })]
public interface IField
{
    string Name { get; set; }
}

请注意,这个解决方案有点脆弱。如果服务器省略了MyStringValueMyValue字段(例如,因为它们具有默认值和DefaultValueHandling = DefaultValueHandling.Ignore ),则转换器将不知道要创建哪种类型,并将抛出异常。类似地,如果实现IField的两个具体类型具有相同的属性名称(仅类型不同),则转换器将抛出一个异常。使用TypeNameHandling.Auto可以避免这些潜在的问题。

更新

下面的版本检查是否存在"$type"参数,以及TypeNameHandling != TypeNameHandling.None是否返回默认序列化。它必须做几个技巧来防止在后退时的无限递归:

代码语言:javascript
复制
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();
    }
}
票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33321698

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档