13

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.

Ron Beyer
  • 11,003
  • 1
  • 19
  • 37
  • OK, Ron, suppose you send this json to an another site. How can it know how you constructed this dictionary(You may even use other methods without any dictionary to create same json) – L.B Sep 25 '17 at 20:16
  • @L.B This application is not web-based, I'm using JSON for serialization over XML for other business reasons. The data will not be handled by an external system. – Ron Beyer Sep 25 '17 at 20:17
  • It is just a example to explain the logic. Receiver of the json has no idea how you created it. It doesn't even know the language you used to create it. So you make wrong assumptions – L.B Sep 25 '17 at 20:19
  • I'm not sure I understand, the system is self-contained, it uses JSON for file persistence of the data only, not to transfer to another system. The receiver of the JSON is always the producer of it as well. The issue is during runtime I need to compare keys in the dictionary in a case-insensitive way, but I can't retrieve the Dictionary with a case insensitive comparer. – Ron Beyer Sep 25 '17 at 20:21
  • Suppose you *DeserializeObject* with this json `{"prop1:"val1"}`. It can be a dictionary or it can be a class with a single property. It could have been serialized with any comparer. So `JsonConvert.DeserializeObject` has no way to know it. – L.B Sep 25 '17 at 20:24
  • Exactly, I'm trying to *tell it* which one to use! :) – Ron Beyer Sep 25 '17 at 20:26

3 Answers3

14

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
Xiaoy312
  • 14,292
  • 1
  • 32
  • 44
11

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);
Rufus L
  • 36,127
  • 5
  • 30
  • 43
5

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>>());
ASpirin
  • 3,601
  • 1
  • 23
  • 32