使用带有 TypeNameHandling 标志的 Json.NET 序列化具有 IConvertible 值的字典

Posted

技术标签:

【中文标题】使用带有 TypeNameHandling 标志的 Json.NET 序列化具有 IConvertible 值的字典【英文标题】:Serializing a dictionary with IConvertible values using Json.NET with the TypeNameHandling-flag 【发布时间】:2019-02-11 18:03:55 【问题描述】:

我有以下字典,我非常想使用 Json.Net 对其进行序列化。字典包含IConvertible 接口的项目,允许我将所需的任何原始类型添​​加到字典中。

    var dic = new Dictionary<string, IConvertible>();
    dic.Add("bool2", false);
    dic.Add("int2", 235);
    dic.Add("string2", "hellohello");

我有以下使用 Json.net 序列化列表的实现:

    var settings = new JsonSerializerSettings();
    settings.TypeNameHandling = TypeNameHandling.Objects;
    var dicString = JsonConvert.SerializeObject(dic,    Newtonsoft.Json.Formatting.Indented, settings);

这给了我以下输出:

    
      "$type": "System.Collections.Generic.Dictionary`2[[System.String,         mscorlib],[System.IConvertible, mscorlib]], mscorlib",
      "bool2": false,
      "int2": 235,
      "string2": "hellohello"
    

但是。当尝试反序列化时:

    var dic2 = JsonConvert.DeserializeObject<Dictionary<string, IConvertible>>(dicString);

...我收到以下错误:

    Error converting value False to type 'System.IConvertible'. Path 'bool2', line 3, position 16.

我环顾四周,发现以下内容;但设置 typeNameHandling 并没有解决它。我也不能用类型名称属性装饰IConvertible 值,因为它是字典。

Casting interfaces for deserialization in JSON.NET

我没有找到有关该主题的任何其他信息,因此非常感谢您的帮助!

我也找到了这个解决方案,但它涉及创建一个 ExpandableObjectConverter,这不是一个非常优雅的解决方案。

Problems using JSON.NET with ExpandableObjectConverter

【问题讨论】:

【参考方案1】:

这里实际上有几个问题:

    在反序列化为IConvertible 的目标类型时,您似乎遇到了一个奇怪的 Json.NET 问题。当反序列化可转换的原始类型时,它calls 系统例程Convert.ChangeType() 将原始类型转换为目标类型(例如longint)。而且,由于某种原因,当被要求将原语转换为 IConvertible 类型时,即使该原语已经属于该类型,该系统例程也会引发异常。

    您正在使用TypeNameHandling.Objects 来序列化您的可转换值字典,但是此设置仅被记录用于序列化为 JSON 对象。但是,您的值将被序列化为 JSON 基元,因此该设置不适用。

    要保留多态原语字典的类型信息,您需要手动将值包装在容器对象中,例如 this answer 到 Deserialize Dictionary<string, object> with enum values in C# 中显示的那个。 (但是,由于问题 #1,该答案在这里不起作用。)

    除非您编写custom serialization binder,否则TypeNameHandling 是不安全的并且容易受到诸如TypeNameHandling caution in Newtonsoft JsonExternal json vulnerable because of Json.Net TypeNameHandling auto? 中所示的小工具注入攻击的攻击/em>.

    反序列化使用的设置与序列化使用的设置不同。

以上问题可以使用以下custom JsonConverter解决:

public class ConvertibleDictionaryConverter : JsonConverter

    [JsonDictionary(ItemTypeNameHandling = TypeNameHandling.Auto)]
    class ConvertibleDictionaryDTO : Dictionary<string, ConvertibleWrapper>
    
        public ConvertibleDictionaryDTO() : base()  

        public ConvertibleDictionaryDTO(int count) : base(count)  
    

    public override bool CanConvert(Type objectType)
    
        return typeof(IDictionary<string, IConvertible>).IsAssignableFrom(objectType);
    

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        var dto = serializer.Deserialize<ConvertibleDictionaryDTO>(reader);
        if (dto == null)
            return null;
        var dictionary = (IDictionary<string, IConvertible>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
        foreach (var pair in dto)
            dictionary.Add(pair.Key, pair.Value.ObjectValue);
        return dictionary;
    

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        var dictionary = (IDictionary<string, IConvertible>)value;
        var dto = new ConvertibleDictionaryDTO(dictionary.Count);
        foreach (var pair in dictionary)
            dto.Add(pair.Key, ConvertibleWrapper.CreateWrapper(pair.Value));
        serializer.Serialize(writer, dto);
    


abstract class ConvertibleWrapper

    protected ConvertibleWrapper()  

    [JsonIgnore]
    public abstract IConvertible ObjectValue  get; 

    public static ConvertibleWrapper CreateWrapper<T>(T value) where T : IConvertible
    
        if (value == null)
            return new ConvertibleWrapper<T>();
        var type = value.GetType();
        if (type == typeof(T))
            return new ConvertibleWrapper<T>(value);
        // Return actual type of subclass
        return (ConvertibleWrapper)Activator.CreateInstance(typeof(ConvertibleWrapper<>).MakeGenericType(type), value);
    


sealed class ConvertibleWrapper<T> : ConvertibleWrapper where T : IConvertible

    public ConvertibleWrapper() : base()  

    public ConvertibleWrapper(T value)
        : base()
    
        this.Value = value;
    

    public override IConvertible ObjectValue  get  return Value;  

    public T Value  get; set; 

然后序列化和反序列化如下:

var settings = new JsonSerializerSettings

    Converters =  new ConvertibleDictionaryConverter() ,
;
var dicString = JsonConvert.SerializeObject(dic, Newtonsoft.Json.Formatting.Indented, settings);

var dic2 = JsonConvert.DeserializeObject<Dictionary<string, IConvertible>>(dicString, settings);

注意事项:

因为[JsonDictionary(ItemTypeNameHandling = TypeNameHandling.Auto)] 应用于ConvertibleDictionaryDTO,所以没有必要全局启用TypeNameHandling.Objects。这可以降低您的安全风险。

限制ConvertibleWrapper&lt;T&gt; 中的对象类型以实现IConvertible 也大大降低了安全风险,因为攻击小工具极不可能实现IConvertible

但是,为了提高安全性,您可能仍希望编写一个仅允许列入白名单的已知类型的自定义序列化绑定器。

工作示例 .Net fiddle here.

【讨论】:

感谢所有信息和工作样本!非常感谢。

以上是关于使用带有 TypeNameHandling 标志的 Json.NET 序列化具有 IConvertible 值的字典的主要内容,如果未能解决你的问题,请参考以下文章

Newtonsoft JSON:TypeNameHandling - $type 用途

如何在带有 for 循环的 bash 脚本中使用标志?

使用带有标志枚举的 ProtoBuf-Net 时出错

使用“sync”分派到队列和使用带有“.wait”标志的工作项之间的区别?

带有 Eigen 和 IPOPT 的矢量化标志

Android - 带有标志的待处理意图