1

Closed

Missing type information when serializing Dictionary<string,object> with TypeNameHandling.All

description

When an object of type Dictionary is serialized the dynamic type of the value is not added to the json string even if TypeNameHandling is set to All. The problem is documented in the unit test below.
[TestMethod]
public void SerializeDeserialize_DictionaryContextContainsGuid_DeserializesItemAsGuid() {
        const string contextKey = "k1";
        var someValue = Guid.NewGuid();

        Dictionary inputContext = new Dictionary();
        inputContext.Add(contextKey, someValue);

        JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() {
                       Formatting = Formatting.Indented,
                       TypeNameHandling = TypeNameHandling.All
         }
        string serializedString = JsonConvert.SerializeObject(inputContext, jsonSerializerSettings);

        Dictionary deserializedObject = JsonConvert.DeserializeObject(serializedString, jsonSerializerSettings);

        Assert.AreEqual(someValue, deserializedObject[contextKey]);
}
In the test above the value of the KeyValuePair is of type Guid. After deserializing the dictionary the value is of type string instead of Guid.

I'm using Json.NET 4.5.11.15520
Closed Jan 30, 2013 at 10:23 PM by JamesNK
I did not realize you had a Dictionary, that wasn't included in your example. I'm afraid Json.NET doesn't support type information against primitive types. Including $type for every single int, DateTime, Guid, string, etc would be too messy.

comments

JamesNK wrote Jan 30, 2013 at 10:07 AM

This works for me:
public void SerializeDeserialize_DictionaryContextContainsGuid_DeserializesItemAsGuid()
{
  const string contextKey = "k1";
  var someValue = Guid.NewGuid();

  Dictionary<string, Guid> inputContext = new Dictionary<string, Guid>();
  inputContext.Add(contextKey, someValue);

  JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings()
    {
      Formatting = Formatting.Indented,
      TypeNameHandling = TypeNameHandling.All
    };
  string serializedString = JsonConvert.SerializeObject(inputContext, jsonSerializerSettings);

  var deserializedObject = (Dictionary<string, Guid>)JsonConvert.DeserializeObject(serializedString, jsonSerializerSettings);

  Assert.AreEqual(someValue, deserializedObject[contextKey]);
}

JamesNK wrote Jan 30, 2013 at 10:07 AM

This works for me:
public void SerializeDeserialize_DictionaryContextContainsGuid_DeserializesItemAsGuid()
{
  const string contextKey = "k1";
  var someValue = Guid.NewGuid();

  Dictionary<string, Guid> inputContext = new Dictionary<string, Guid>();
  inputContext.Add(contextKey, someValue);

  JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings()
    {
      Formatting = Formatting.Indented,
      TypeNameHandling = TypeNameHandling.All
    };
  string serializedString = JsonConvert.SerializeObject(inputContext, jsonSerializerSettings);

  var deserializedObject = (Dictionary<string, Guid>)JsonConvert.DeserializeObject(serializedString, jsonSerializerSettings);

  Assert.AreEqual(someValue, deserializedObject[contextKey]);
}

studix wrote Jan 30, 2013 at 7:47 PM

Thanks James for your answer.

You're right, changing the declared type of the value to Guid results in the same serialized and deserialized value. But that doesn't help me. I cannot change the declaring type from Dictionary<string,object> to Dictionary<string, Guid>. Multiple types of values are held in the dictionary.

Whenever the declaring type of a member is object and the runtime type is a value type it results in a mismatch of original and serialized/deserialized type. Another example with Timespan is also reported on StackOverflow. When the declaring type of a member is object and the runtime type is a reference type everything works.

I suggest that you also write type information for value types, not only for reference types

JamesNK wrote Jan 30, 2013 at 7:48 PM

See comment

** Closed by JamesNK 1/30/2013 3:08 AM

studix wrote Jan 30, 2013 at 7:48 PM

see comment

studix wrote Feb 1, 2013 at 7:46 AM

You're right with TypeNameHandling.All the serialized output will be messy. But this is also now the case with this setting. With TypeNameHandling.Auto and TypeNameHandling.None I think there will be not much difference when you would also serialize the type information for simple types.

But I accept when you say that you don't want to support that.

aggieNick02 wrote Apr 8, 2013 at 5:56 PM

The underlying issue is, imho, really a bug. It occurs whenever you have a Dictionary of object values (key types don't matter) and some value is of a type that DefaultContractResolver returns true for CanConvertToString(). It makes it really dangerous to use because it works for all types except those that can convert to string. I guess we can always write our own ContractResolver to get around this.

AttachedWPF wrote Mar 26 at 3:48 PM

This is still a problem. We have a system where we have self tracking entities that has a value, originalvalues and previousvalue Dictionary<string, object> to store the property values. Whenever we have an int in the collections and serialized when its deserialized its coming out as a long and breaking a lot of our casting. Is there a workaround to solve this problem?