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

5
我有一个字典,非常希望能使用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中反序列化接口的转换,请参考以下链接: Casting interfaces for deserialization in JSON.NET 我没有找到有关该主题的其他信息,因此非常需要帮助!
我还发现了这个解决方案,但它涉及创建ExpandableObjectConverter,这不是一个非常优雅的解决方案。
请参考以下链接: Problems using JSON.NET with ExpandableObjectConverter
1个回答

5

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

  1. 当您将Json.NET反序列化为IConvertible目标类型时,似乎遇到了奇怪的问题。 当反序列化可转换的基元类型时,它调用系统例程{{link2:Convert.ChangeType()}}将基元转换为目标类型(例如将long转换为int)。出于某种原因,当要求将基元转换为类型IConvertible时,此系统例程会引发异常,尽管该基元已经属于该类型。

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

    为了保留多态基元的字典的类型信息,您需要手动将值包装在容器对象中,例如此答案所示的容器对象。 在C#中使用枚举值反序列化字典<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; }
}

然后按以下步骤进行序列化和反序列化:
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


感谢您提供的所有信息和工作样本!非常感激。 - user1531921

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接