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

use*_*921 5 c# json json.net iconvertible jsonconvert

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

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

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

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

这给了我以下输出:

    {
      "$type": "System.Collections.Generic.Dictionary`2[[System.String,         mscorlib],[System.IConvertible, mscorlib]], mscorlib",
      "bool2": false,
      "int2": 235,
      "string2": "hellohello"
    }
Run Code Online (Sandbox Code Playgroud)

然而。当尝试这样反序列化时:

    var dic2 = JsonConvert.DeserializeObject<Dictionary<string, IConvertible>>(dicString);
Run Code Online (Sandbox Code Playgroud)

...我收到以下错误:

    Error converting value False to type 'System.IConvertible'. Path 'bool2', line 3, position 16.
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

将 JSON.NET 与 ExpandableObjectConverter 结合使用时出现问题

dbc*_*dbc 5

实际上你这里有几个问题:

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

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

    要保留多态基元字典的类型信息,您需要手动将值包装在容器对象中,例如在 C# 中使用枚举值反序列化 Dictionary<string, object> 的答案中所示对象。(然而,由于问题#1,这个答案在这里不起作用。)

  3. 除非您编写自定义序列化绑定器,否则TypeNameHandling是不安全的并且容易受到小工具注入攻击,例如Newtonsoft Json 中的 TypeNameHandling 警告中所示的攻击,以及由于 Json.Net TypeNameHandling auto 而导致外部 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; }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

笔记:

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

  • ConvertibleWrapper<T>限制要实施的对象类型IConvertible还可以大大降低安全风险,因为攻击工具不太可能实施IConvertible

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

工作示例 .Net fiddle在这里