Not ableTo Serialize Dictionary with Complex key using Json.net

21,185

Solution 1

You probably don't want to use the answer that Gordon Bean presented. The solution works, but it provides a serialized string for output. If you are using JSON, this will give you a less than ideal result, since you really want a JSON representation of an object and not a string representation.

for example, let's suppose that you have a data structure that associates unique grid points with strings:

class Point
{
    public int x { get; set; }
    public int y { get; set; }
}

public Dictionary<Point,string> Locations { get; set; };

Using the TypeConverter override, you will get a string representation of this object when you serialize it.

"Locations": {
  "4,3": "foo",
  "3,4": "bar"
},

But what we really want is:

"Locations": {
  { "x": 4, "y": 3 }: "foo",
  { "x": 3, "y": 4 }: "bar"
},

There are several problems with overriding the TypeConverter to serialize / deserialize the class.

First, this is not JSON, and you might have to write additional custom logic to deal with serialize and deserialize it elsewhere. (perhaps Javascript in your client layer, for example?)

Second, Anywhere else that uses this object will now spew this string, where previously it serialized properly to an object:

"GridCenterPoint": { "x": 0, "y": 0 },

now serializes to:

"GridCenterPoint": "0,0",

You can control the TypeConverter formatting a little, but you cannot get away from the fact it is rendered as a string and not an object.

This problem isn't a problem with the serializer, since Json.NET chews through complex objects without missing a beat, it is a problem with the way that dictionary keys are processed. If you try taking the example object, and serializing a List or even a Hashset, you notice that there isn't a problem producing proper JSON. This gives us a much simpler way to solve this problem.

Ideally, we would like to just tell Json.NET to serialize the key as whatever object type it is, and not force it to be a string. Since that doesn't seem to be an option, the other way is to give Json.NET something that it can work with: a List<KeyValuePair<T,K>>.

If you feed a list of KeyValuePairs into Json.NET's serializer, you get exactly what you expect. For example, here is a much simpler wrapper that you could implement:

    private Dictionary<Point, string> _Locations;
    public List<KeyValuePair<Point, string>> SerializedLocations
    {
        get { return _Locations.ToList(); }
        set { _Locations= value.ToDictionary(x => x.Key, x => x.Value); }
    }

This trick works, because keys in a kvp aren't forced into string format. Why a string format, you ask? It beats the hell out of me. the Dictionary object implements the IEnumerable<KeyValuePair<TKey, TValue>> interface, so there shouldn't be any problem in serializing it in the same fashion as the list of kvps, since that is essentially what a dictionary is. Someone (James Newton?) made a decision when writing the Newtonsoft dictionary serializer that complex keys were too messy to deal with. There are probably some corner cases I have not considered that make this a much more sticky problem.

This is a much better solution because it produces actual JSON objects, is technically simpler, and doesn't produce any side effects resulting from replacing the serializer.

Solution 2

The Serialization Guide states (see section: Dictionaries and Hashtables; thank you @Shashwat for the link):

When serializing a dictionary, the keys of the dictionary are converted to strings and used as the JSON object property names. The string written for a key can be customized by either overriding ToString() for the key type or by implementing a TypeConverter. A TypeConverter will also support converting a custom string back again when deserializing a dictionary.

I found a useful example for how to implement such a type converter on Microsoft's "how-to" page:

Essentially, I needed to extend System.ComponentModel.TypeConverter and override:

bool CanConvertFrom(ITypeDescriptorContext context, Type source);

object ConvertFrom(ITypeDescriptorContext context,
                   System.Globalization.CultureInfo culture, object value);

object ConvertTo(ITypeDescriptorContext context, 
                 System.Globalization.CultureInfo culture, 
                 object value, Type destinationType);

It was also necessary to add the attribute [TypeConverter(typeof(MyClassConverter))] to the MyClass class declaration.

With these in place, I was able to serialize and deserialize dictionaries automatically.

Solution 3

Another way to accomplish this is to use a custom ContractResolver and setting OverrideCreator.

public class DictionaryAsArrayResolver : DefaultContractResolver
{
    public override JsonContract CreateContract(Type objectType)
    {
        if (IsDictionary(objectType))
        {
            JsonArrayContract contract = base.CreateArrayContract(objectType);
            contract.OverrideCreator = (args) => CreateInstance(objectType);
            return contract;
        }

        return base.CreateContract(objectType);
    }

    internal static bool IsDictionary(Type objectType)
    {
        if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
        {
            return true;
        }

        if (objectType.GetInterface(typeof(IDictionary<,>).Name) != null)
        {
            return true;
        }

        return false;
    }

    private object CreateInstance(Type objectType)
    {
        Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(objectType.GetGenericArguments());
        return Activator.CreateInstance(dictionaryType);
    }
}

Usage:

JsonSerializer jsonSerializer = new JsonSerializer();
jsonSerializer.ContractResolver = new DictionaryAsArrayResolver();

Solution 4

After @roger-hill answer I came up with a lightweight solution to achieve the same result:

    [JsonArray]
    public class MyDictionary<K, V> : Dictionary<K, V>
    {
    }

In this way every MyDictionary object gets serialized as an array of Key/Value pair, behaving properly also with complex key type:

[{
    "Key": ...,
    "Value": ...
}, ...]
Share:
21,185
Shashwat Gupta
Author by

Shashwat Gupta

Updated on July 09, 2022

Comments

  • Shashwat Gupta
    Shashwat Gupta almost 2 years

    I have a dictionary with a custom .net Type as Its key.I am trying to serialize this dictionary to JSON using JSON.net, However its not able to Convert Keys to Proper Value during Serialization.

    class ListBaseClass
    {
        public String testA;
        public String testB;
    }
    -----
    var details = new Dictionary<ListBaseClass, string>();
    details.Add(new ListBaseClass { testA = "Hello", testB = "World" }, "Normal");
    var results = Newtonsoft.Json.JsonConvert.SerializeObject(details);
    var data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<ListBaseClass, string>> results);
    

    This Give me --> "{\"JSonSerialization.ListBaseClass\":\"Normal\"}"

    However if I have my Custom type as value in Dictionary it Works well

      var details = new Dictionary<string, ListBaseClass>();
      details.Add("Normal", new ListBaseClass { testA = "Hello", testB = "World" });
      var results = Newtonsoft.Json.JsonConvert.SerializeObject(details);
      var data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, ListBaseClass>>(results);
    

    This Give me --> "{\"Normal\":{\"testA\":\"Hello\",\"testB\":\"World\"}}"

    Can Someone Suggest If I am hitting some limitation of Json.net or I am doing Something Wrong?

  • mungflesh
    mungflesh about 8 years
    This worked for us, to resolve a problem with complex Dictionary keys. Where a generic class was being used twice with different concrete implementations in the class nesting, our custom JsonConverter did not work using the declarative attribute on the complex key class - we had to pass it as an argument to JsonConvert.SerializeObject, which worked but was less desirable.
  • StayOnTarget
    StayOnTarget about 7 years
    I also just got this working for a complex Dictionary key type. Note for others - this is independant of the JSONTypeConverter stuff in the JSON.Net package. Also, there were no JSON settings which needed to be used for this. This answer really does contain exactly what you need to do. I found it useful to put breakpoints inside the overriden functions initially, to gain better understanding of when they were used & what they were asked to do.
  • user3141326
    user3141326 about 7 years
    Great answer, even though it did not work for me, since I am using windows phone 8.1 where System.ComponentModel.TypeConverter is not supported.
  • Aditya
    Aditya about 6 years
    could you please add some more details on how this solves OPs error?
  • bmw15
    bmw15 almost 4 years
    I made a few tweaks to this solution and found it to be the fastest to implement and also correct in the sense @roger-hill mentions. My tweaks were to make the Dictionary prop public, add a [JsonIgnore] to it, and make the list of KeyValuePairs private, with a [JsonProperty] attribute. I used a private [JsonConstructor] that accepted the list of keyValuePairs, but I imagine it would work with just the [JsonProperty] attribute as well.
  • TheForgot3n1
    TheForgot3n1 almost 4 years
    @bmw15 It looks like it does not work with just the JsonProperty. You need to have the constructor set the list of KeyValuePairs explicitly, or JsonConvert simply ignores the property, even with [JsonProperty]. I tried both your approach and Roger Hill's.
  • Tal Aloni
    Tal Aloni over 3 years
    An even better way is to map IDictionary<,> to Dictionary<,> (when calling CreateArrayContract), this would prevent Newtonsoft.Json from adding the Dictionary type information. this would also make setting the OverrideCreator not necessary.