2013-04-08 43 views
4

Json.NET並不總是適當地考慮宣稱的ValueTypeDictionary<KeyType,ValueType>Json.NET詞典序列化中的重大錯誤

這使得序列化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! 

我在想這個錯誤還是錯過了什麼?我只是切換到Json.NET從XmlSerializer的,因爲它是令人難以置信的更好的性能(尤其是在建設中),它是所有很容易過渡到,但運行到這個問題讓我害怕了一下。

看來,如果Json.NET將把某些東西寫成字符串,因爲對象類型爲CanConvertToString()返回true,它需要寫出一個Json屬性,指出發生了對字符串的轉換,以便它可以在反序列化中可靠地「未轉換」...

+0

什麼'詞典<字符串,Rect>'做反序列化? – asawyer 2013-04-08 17:49:31

+0

它會工作。問題是當你爲你的值有一堆不同的對象類型的字典時。 – aggieNick02 2013-04-08 19:10:22

回答

0

當您反序列化爲Dictionary<string, object> Json.Net在確定實例化字典值時沒有任何類型信息。正常的解決方案是要麼使用強類型容器(例如Dictionary<string, Rect>)或所述TypeNameHandling選項在串行設置爲Objects。後者將告訴Json.Net輸出類型的元數據與JSON,這樣,當它被反序列化它知道實例化哪一個類型。

然而,某些類型的像System.Windows.Rect標有[TypeConverter]屬性。當Json.Net找到這樣的類型,它使用相關聯的TypeConverter序列化對象到字符串,而該處理它像一個正常的對象。不幸的是,當這種轉換髮生時,原始類型信息會丟失,因此沒有元數據會被寫出來。這意味着,除非你是反序列化到一個強類型的類或容器,你會得到一個字符串返回,而你的原始對象,而你又回到了起點。

您可以通過使用自定義的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 
+0

我應該包括我已經嘗試過。對於從'CanConvertToString()'返回true的類型,Json.NET會忽略TypeNameHandling。我會更新這個問題來反映這一點。 – aggieNick02 2013-04-08 19:07:17