当前位置: 首页 > 面试题库 >

带接口的JsonConverter

施海
2023-03-14
问题内容

我有一个来自客户端的对象,并自动从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),但是我只是不知道如何。

我的Converter被调用,当我调用var obj = JObject.load(reader);时,我可以看到该对象。

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

if(obj is Field1) ...

这是我应该检查此方法的方法吗?

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

问题答案:

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


 类似资料:
  • 问题内容: 根据Hibernate文档的这一部分,我应该能够查询HQL中的任何Java类。 http://docs.jboss.org/hibernate/core/3.3/reference/en/html/queryhql.html#queryhql- polymorphism 不幸的是,当我运行此查询时… 我收到消息“ [未映射事务[从事务trans,其中trans.envelopeId =

  • 问题内容: 我已经实现了ListSelectionListener,如下所示,因此在选择了第一个表中的特定行之后,第二个表将进行相应的更新。 有没有一种方法不仅可以在选择鼠标时,而且可以在通过键盘进行选择时调用侦听器? 问题答案: 出现异常体验的原因-没有通过键盘选择的通知-是valueIsAdjusting针对键盘与鼠标触发的选择事件的微妙不同设置: 键盘触发的选择(即使带有修饰符)仅触发一次(

  • 我有一个DAO接口,其中有多个实现,我希望其中一个是Room实现(Kotlin):

  • 问题内容: 有没有理由不将Controller映射为接口? 在所有的示例和问题中,我看到了周围的控制器,都是具体的类。是否有一个原因?我想将请求映射与实现分开。但是,当我尝试在具体类中获取a 作为参数时,我碰壁了。 我的Controller界面如下所示: And the implementing class: 该方法效果很好;在抛出一个异常 如果我将注释添加到具体类中,那么一切都会按预期工作,但是