首页 > 解决方案 > 使用带有 TypeNameHandling 标志的 Json.NET 序列化具有 IConvertible 值的字典

问题描述

我有以下字典,我非常想使用 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类型名称属性来装饰值,因为它是字典。

在 JSON.NET 中反序列化的转换接口

我还没有找到有关该主题的任何其他信息,因此将不胜感激!

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

使用带有 ExpandableObjectConverter 的 JSON.NET 的问题

标签: c#jsonjson.neticonvertiblejsonconvert

解决方案


你实际上有几个问题:

  1. 在反序列化为IConvertible. 当反序列化可转换的原始类型时,它调用系统例程将原始类型Convert.ChangeType()转换为目标类型(例如long转换为int)。IConvertible而且,由于某种原因,当被要求将原语转换为 type时,即使该原语已经属于该类型,该系统例程也会引发异常。

  2. 您正在使用TypeNameHandling.Objects序列化可转换值的字典,但是仅记录此设置以工作序列化为 JSON对象。但是,您的值将被序列化为 JSON 原语,因此该设置不适用。

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

  3. 除非您编写自定义序列化绑定器,否则是否TypeNameHandling不安全且容易受到小工具注入攻击,例如Newtonsoft Json 中的 TypeNameHandling 警告由于 Json.Net TypeNameHandling auto 而易受攻击的 External json 中所示的那些?.

  4. 您没有使用与序列化相同的设置来反序列化。

使用以下自定义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<T>实施对象的类型IConvertible也大大降低了安全风险,因为攻击小工具极不可能实施IConvertible

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

工作示例 .Net在这里摆弄。


推荐阅读