3

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??

Fred
  • 3,365
  • 4
  • 36
  • 57
  • 1
    Do you need the dspecial setting and the Guid? Maybe try something simpler like `JsonConvert.SerializeObject(dic);` and see if that gives you what you need. – senschen Mar 25 '16 at 15:10
  • @senschen I use a generic class to serialize and deserialize multiple types in my project. other types in my project needs to this settings. please read last edit of my question – Fred Mar 25 '16 at 15:24
  • Use string instead of Guid @Fred – Zehra Subaş Mar 25 '16 at 15:55
  • @ZehraSubaş In last edit of my question I do it,but .. – Fred Mar 25 '16 at 16:01

1 Answers1

4

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.

dbc
  • 104,963
  • 20
  • 228
  • 340