Fred
Fred

Reputation: 3431

Newtonsoft.Json, Populate Dictionary failed

I serialize a dictionary to json by Newtonsoft.json and bellow code :

var serializeSettings = new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.All,
            TypeNameAssemblyFormat = FormatterAssemblyStyle.Full,
            Formatting = Formatting.Indented
        };
        var serializedObject = JsonConvert.SerializeObject(dic, serializeSettings);

this code generate a json like this :

{
  "$type": "System.Collections.Generic.Dictionary`2[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
  "9648af76-7986-4b34-8b2c-97b2345769ef": "Test"
}

I try to deserialize json to dictionary by this code :

var newDic = new Dictionay<Guid,string>();
var deserializeSettings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All,
    TypeNameAssemblyFormat = FormatterAssemblyStyle.Full,
    Formatting = Formatting.Indented
}
JsonConvert.PopulateObject(serializedObject, newDic, deserializeSettings);

But this exception occurs :

Could not convert string '$type' to dictionary key type 'System.Guid'. Create a TypeConverter to convert from the string to the key type object. Path '$type', line 2, position 10.

at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateDictionary(IDictionary dictionary, JsonReader reader, JsonDictionaryContract contract, JsonProperty containerProperty, String id)

at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Populate(JsonReader reader, Object target)

at Newtonsoft.Json.JsonSerializer.PopulateInternal(JsonReader reader, Object target)

at Newtonsoft.Json.JsonSerializer.Populate(JsonReader reader, Object target)

at Newtonsoft.Json.JsonConvert.PopulateObject(String value, Object target, JsonSerializerSettings settings)

I write GuidConverter like this and use it. but not work

public class GuidConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsAssignableFrom(typeof(Guid));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            return serializer.Deserialize<Guid>(reader);
        }
        catch
        {
            return Guid.Empty;
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

EDIT:

I found my problem. Change code to deserialize json to Dictionary< string, string > and now First item in generated dictionary is :

Kay: "$type"
Value : "System.Collections.Generic.Dictionary`2[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

Why??

Upvotes: 3

Views: 2895

Answers (1)

dbc
dbc

Reputation: 116981

The problem is that you have specified TypeNameHandling = TypeNameHandling.All when serializing your dictionary. This causes a metadata "$type" property to be emitted as the first object in the dictionary:

{
  "$type": "System.Collections.Generic.Dictionary`2[[System.Guid, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
  "9648af76-7986-4b34-8b2c-97b2345769ef": "Test"
}

When deserializing using DeserializeObject, this token is normally consumed by Json.NET when the corresponding c# object is constructed. But you are using PopulateObject on a pre-allocated dictionary. Thus the metadata property is not consumed during construction and instead Json.NET tries to add it to the dictionary, and fails.

The solution is to set MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead in deserializeSettings. Doing so will cause the "$type" property to be consumed or ignored (as appropriate) unconditionally:

var deserializeSettings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All,
    TypeNameAssemblyFormat = FormatterAssemblyStyle.Full,
    Formatting = Formatting.Indented,
    MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead
};
JsonConvert.PopulateObject(serializedObject, newDic, deserializeSettings);

Please note that, from the release notes, there is a slight cost in memory usage and speed from using this setting.

Alternatively, if you don't unconditionally need metadata type information in your JSON, you could serialize with TypeNameHandling = TypeNameHandling.Auto and only emit type information for polymorphic types, which your Dictionary<Guid, string> is not.

Relatedly, when using TypeNameHandling, do take note of this caution from the Newtonsoft docs:

TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.

For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json.

Upvotes: 4

Related Questions