Not ableTo Serialize Dictionary with Complex key using Json.net

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?


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:

  • Implement a Type Converter (see section Type Converters for Value Translation).

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.