c# - 使用带有 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
类型名称属性来装饰值,因为它是字典。
我还没有找到有关该主题的任何其他信息,因此将不胜感激!
我也找到了这个解决方案,但它涉及创建一个 ExpandableObjectConverter 这不是一个非常优雅的解决方案。
解决方案
你实际上有几个问题:
在反序列化为
IConvertible
. 当反序列化可转换的原始类型时,它调用系统例程将原始类型Convert.ChangeType()
转换为目标类型(例如long
转换为int
)。IConvertible
而且,由于某种原因,当被要求将原语转换为 type时,即使该原语已经属于该类型,该系统例程也会引发异常。您正在使用
TypeNameHandling.Objects
序列化可转换值的字典,但是仅记录此设置以工作序列化为 JSON对象。但是,您的值将被序列化为 JSON 原语,因此该设置不适用。要保留多态原语字典的类型信息,您需要手动将值包装在容器对象中,例如在 C# 中使用枚举值对Deserialize Dictionary<string, object> 的答案中显示的那个。(但是,由于问题 #1,该答案在这里不起作用。)
除非您编写自定义序列化绑定器,否则是否
TypeNameHandling
不安全且容易受到小工具注入攻击,例如Newtonsoft Json 中的 TypeNameHandling 警告和由于 Json.Net TypeNameHandling auto 而易受攻击的 External json 中所示的那些?.您没有使用与序列化相同的设置来反序列化。
使用以下自定义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在这里摆弄。
推荐阅读
- php - Laravel with() 和使用 LIKE 搜索不起作用
- angularjs - 设置输入默认值并能够在以后编辑它 - AngularJS
- java - 如何修改此代码以将随机文本插入控制台并为我反转它?
- c# - WPF:如何在没有任何灰色区域的网格中平均分配 2 个扩展器控件之间的可用高度
- javascript - 在 React 应用程序中实现本地存储
- typescript - 对象可能为“空”。在 ref(null) 上
- java - 从 Java 中的一个抽象类扩展而来的不同类中的不同变量
- reactjs - Jest - 测试文件外的模拟模块
- python-3.x - Save updatable variable into another that will not change
- r - cumsum 有几个组和不连续的日期