Deserialize JSON Dictionary with StringComparer

Viewed 3504

I'm trying to serialize/deserialize a dictionary, the problem is that I create the dictionary with a StringComparer.OrdinalIgnoreCase comparer.

Here is a code snippet of the problem I'm experiencing:

var dict = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);

dict["x"] = new Dictionary<string, string>();
dict["x"]["y"] = "something";

var serialized = JsonConvert.SerializeObject(dict);

var unSerialized = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string>>>(serialized);

Console.WriteLine((dict.Comparer == unSerialized.Comparer ? "Same" : "Different"));

(.NET Fiddle - Try It)

Prints out the following on the console:

Different

Obviously the JSON serializer doesn't serialize the Comparer that I set when I create the dictionary, but the issue is that I can't set the Comparer after the fact since Dictionary<TKey, TValue>.Comparer is read-only.

I'm sure it has to do with some custom JsonSerializerSetting but I can't seem to figure out how to intercept the collection creation and return a dictionary with a different comparer.

3 Answers

You can also populate an existing object with PopulateObject:

var dict = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);

dict["x"] = new Dictionary<string, string>();
dict["x"]["y"] = "something";

var json = JsonConvert.SerializeObject(dict);


var result =  new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
JsonConvert.PopulateObject(json, result);

Console.WriteLine(result["x"]["y"]);
Console.WriteLine(result.Comparer == dict.Comparer ? "Same" : "Diff");

Output:

something
Same

You can specify the comparer to use in the constructor of your dictionary if you pass both the result of the deserialization and the comparer you want to use to the constructor of a new dictionary:

var deserialized = JsonConvert
    .DeserializeObject<Dictionary<string, Dictionary<string, string>>>(serialized);

var withComparer = new Dictionary<string, Dictionary<string, string>> (
    deserialized, StringComparer.OrdinalIgnoreCase);

It's a bit late probably, but it is possible to extend generated JSON using JsonConverter will be a bit more complex, but more flexible. I've created a sample for the described case, it is not full
.NET Fiddle - Try It
(feel free to extend if you would decide to use it):

public class DictConverter<TValue> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var obj = JToken.ReadFrom(reader);                
        if (objectType == typeof(Dictionary<string, TValue>))
        {
            var comparer = obj.Value<string>("Comparer");
            Dictionary<string, TValue> result;
            if (comparer == "OrdinalIgnoreCase")
            {
                result = new Dictionary<string, TValue>(StringComparer.OrdinalIgnoreCase);
            }
            else
            {
                result = new Dictionary<string, TValue>();
            }
            obj["Comparer"].Parent.Remove();
            serializer.Populate(obj.CreateReader(), result);
            return result;
        }
        return obj.ToObject(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var obj = JObject.FromObject(value);
        if (value is Dictionary<string, TValue>)
        {
            if((value as Dictionary<string, TValue>).Comparer == StringComparer.OrdinalIgnoreCase)
                obj.Add("Comparer", JToken.FromObject("OrdinalIgnoreCase"));
        }
        obj.WriteTo(writer);

    }
}

and usage

var dict = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);

dict["x"] = new Dictionary<string, string>();
dict["x"]["y"] = "something";

var serialized = JsonConvert.SerializeObject(dict, 
    new DictConverter<Dictionary<string,string>>());
var unSerialized = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string>>>
    (serialized, new DictConverter<Dictionary<string, string>>());
Related