5

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

Dictionary<string,object>如果一个值恰好是DefaultContractResolver.CanConvertToString()返回 true 的值,除非我遗漏了一些东西,否则这使得序列化非常不可行。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 属性来指示发生了到字符串的转换,以便它可以可靠地“未转换”关于反序列化...

4

1 回答 1

0

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

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

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

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
于 2013-04-08T18:50:06.213 回答