Deserialize JSON Dictionary with StringComparer
Asked Answered
P

3

14

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.

Pitzer answered 25/9, 2017 at 20:9 Comment(6)
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)Longlimbed
@Longlimbed 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.Pitzer
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 assumptionsLonglimbed
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.Pitzer
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.Longlimbed
Exactly, I'm trying to tell it which one to use! :)Pitzer
I
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
Ichnography answered 25/9, 2017 at 20:21 Comment(0)
B
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);
Burn answered 25/9, 2017 at 20:20 Comment(2)
Thank you, I didn't think to return it as a collection in the constructor of the dictionary.Pitzer
Note: the inner Dictionaries will still not be case-invariant.Cinder
E
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>>());
Expect answered 25/9, 2017 at 21:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.