Significant error in serialization of Json.NET dictionary

Json.NET does not always correctly account for declared ValueType in Dictionary<KeyType,ValueType> .

This makes Dictionary<string,object> serialization quite inoperative if the value turns out to be such for which DefaultContractResolver.CanConvertToString() returns true if I don’t miss something. Rect is one of these types in .NET 4.0. I tried this on Json.NET 4.5r11 and 5.0r2. Consider the following code:

 _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! 

Am I thinking about it wrong or is something missing? I just switched to Json.NET from XmlSerializer, because it is incredibly more efficient (especially when building), and everything was pretty easy to switch over, but doing this problem scares me a bit.

It seems that if Json.NET will write something as a string, because the object type returns true for CanConvertToString (), it needs to write a Json attribute indicating that the conversion to string has occurred, can be reliably "not converted" during deserialization ...

+4
source share
1 answer

When you deserialize to Dictionary<string, object> Json.Net does not have any type information that will be used to determine what needs to be created for the dictionary values. The usual solution is to either use a strongly typed container (for example, Dictionary<string, Rect> ), or set the TypeNameHandling option to Objects in the serializer. The latter will inform Json.Net of the release of type metadata with JSON so that when deserializing it knows what type needs to be created.

However, some types, such as System.Windows.Rect , are marked with the [TypeConverter] attribute. When Json.Net finds this type, it uses the associated TypeConverter to serialize the object into a string, rather than treat it like a regular object. Unfortunately, when this conversion occurs, the original type information is lost, so no metadata is written for the value. This means that if you do not deserialize a strongly typed class or container, you will get the string back, not your original object, and you will return to the square.

You can get around this problem by using a custom ContractResolver, which causes Json.Net to serialize Rect normally instead of using its TypeConverter. Here is the code you need:

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

Here's a circular demo using the modified code from your question:

 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); 

Output:

 { "$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
source

All Articles