Json.NET字典序列化中存在重大错误



Json.NET并不总是正确考虑Dictionary<KeyType,ValueType>中声明的ValueType

如果某个值恰好是DefaultContractResolver.CanConvertToString()返回true的值,那么序列化Dictionary<string,object>就非常不可行,除非我遗漏了什么。Rect就是.NET 4.0中的一种类型。我在Json.NET 4.5r11和5.0r2中尝试过这个。考虑以下代码:

_requestSerializerJson = new JsonSerializer();
// Even setting TypeNameHandling to All doesn't change the deserialized result
Dictionary<string, object> dictionary = new Dictionary<string, object>();
Rect a = new Rect(1, 2, 3, 4);
dictionary.Add("myrect", a);
byte[] bytes;
using (MemoryStream requestStream = new MemoryStream())
using (var streamWriter = new StreamWriter(requestStream))
using (var writer = new JsonTextWriter(streamWriter))
{
    _requestSerializerJson.Serialize(writer, dictionary);
    writer.Flush();
    bytes = requestStream.ToArray();
}
// Serialized to: {"myrect":"1,2,3,4"}
using (MemoryStream stream = new MemoryStream(bytes, 0, bytes.Length))
using (var textReader = new StreamReader(stream))
using (var reader = new JsonTextReader(textReader))
{
    var b = _requestSerializerJson.Deserialize<Dictionary<string, object>>(reader);
}
// b is a Dictionary with a single *string* value "1,2,3,4" instead of a Rect!

我是不是想错了还是错过了什么?我刚从XmlSerializer切换到Json.NET,因为它的性能非常高(尤其是在构建时(,而且很容易转换到它,但遇到这个问题让我有点害怕。

如果Json.NET要将某些内容写为字符串,因为对象类型为CanConvertToString((返回true,则它需要写出一个Json属性,指示发生了到字符串的转换,以便在反序列化时可以可靠地"未转换"。。。

当您反序列化到Dictionary<string, object>时,Json.Net在确定为字典值实例化什么时没有任何类型信息。通常的解决方案是使用强类型容器(例如Dictionary<string, Rect>(,或者在序列化程序上将TypeNameHandling选项设置为Objects。后者将告诉Json.Net使用Json输出类型元数据,以便在反序列化时知道要实例化哪个类型。

但是,某些类型(如System.Windows.Rect(标记有[TypeConverter]属性。当Json.Net找到这样的类型时,它会使用关联的TypeConverter将对象序列化为字符串,而不是将其视为普通对象。不幸的是,当发生这种转换时,原始类型信息会丢失,因此不会为该值写入元数据。这意味着,除非你正在反序列化到一个强类型的类或容器,否则你会得到一个字符串,而不是你的原始对象,然后你就回到了原点。

您可以通过使用自定义ContractResolver来解决此问题,该ContractResolver强制Json.Net正常序列化Rect,而不是使用其TypeConverter。这是您需要的代码:

class CustomResolver : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        if (objectType == typeof(System.Windows.Rect))
            return CreateObjectContract(objectType);
        return base.CreateContract(objectType);
    }
}

以下是一个使用您的问题中修改的代码的往返演示:

JsonSerializer _requestSerializerJson = new JsonSerializer();
_requestSerializerJson.TypeNameHandling = TypeNameHandling.Objects;
_requestSerializerJson.ContractResolver = new CustomResolver();
_requestSerializerJson.Formatting = Formatting.Indented;
Dictionary<string, object> dictionary = new Dictionary<string, object>();
System.Windows.Rect a = new System.Windows.Rect(1, 2, 3, 4);
dictionary.Add("myrect", a);
byte[] bytes;
using (MemoryStream requestStream = new MemoryStream())
using (var streamWriter = new StreamWriter(requestStream))
using (var writer = new JsonTextWriter(streamWriter))
{
    _requestSerializerJson.Serialize(writer, dictionary);
    writer.Flush();
    bytes = requestStream.ToArray();
}
Console.WriteLine(Encoding.UTF8.GetString(bytes));
Console.WriteLine();
Dictionary<string, object> b;
using (MemoryStream stream = new MemoryStream(bytes, 0, bytes.Length))
using (var textReader = new StreamReader(stream))
using (var reader = new JsonTextReader(textReader))
{
    b = _requestSerializerJson.Deserialize<Dictionary<string, object>>(reader);
}
System.Windows.Rect rect = (System.Windows.Rect)b["myrect"];
Console.WriteLine("Left: " + rect.Left);
Console.WriteLine("Top: " + rect.Top);
Console.WriteLine("Width: " + rect.Width);
Console.WriteLine("Height: " + rect.Height);

输出:

{
  "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib",
  "myrect": {
    "$type": "System.Windows.Rect, WindowsBase",
    "IsEmpty": false,
    "Location": "1,2",
    "Size": "3,4",
    "X": 1.0,
    "Y": 2.0,
    "Width": 3.0,
    "Height": 4.0,
    "Left": 1.0,
    "Top": 2.0,
    "Right": 4.0,
    "Bottom": 6.0,
    "TopLeft": "1,2",
    "TopRight": "4,2",
    "BottomLeft": "1,6",
    "BottomRight": "4,6"
  }
}
Left: 1
Top: 2
Width: 3
Height: 4

最新更新