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