使用 Json.NET 和 TypeNameHandling-flag 序列化具有 IConvertible 值的字典

Serializing a dictionary with IConvertible values using Json.NET with the TypeNameHandling-flag

我有以下字典,我非常想使用 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 the system routine Convert.ChangeType() 将原始类型转换为目标类型(例如 longint)。而且,出于某种原因,当被要求将原语转换为类型 IConvertible 时,该系统例程抛出异常,即使该原语已经是该类型。

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

    要保留多态基元字典的类型信息,您需要手动将值包装在容器对象中,例如 to 中所示的对象。 (但是,由于问题 #1,该答案在这里不起作用。)

  3. 除非你写一个custom serialization binder, TypeNameHandling is insecure and vulnerable to attack gadget injection attacks such as the ones shown in and .

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

以上问题可以通过使用以下方法解决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<T> 中的对象类型以实现 IConvertible 也大大降低了安全风险,因为攻击小工具极不可能实现 IConvertible

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

工作示例 .Net fiddle here.