Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Json.NET Dictionary<string,T> with StringComparer serialization

Tags:

I have a dictionary Dictionary<string, Dictionary<string, object>>. Both the outer dictionary and the inner one have an equality comparer set(in my case it is StringComparer.OrdinalIgnoreCase). After the dictionary is serialized and deserialized the comparer for both dictionaries is not set to StringComparer.OrdinalIgnoreCase.

If you have control over the creation of the dictionaries in your code, you can create a class inherited from the dictionary and set comparer in the default constructor of the class. But what if you do not have control over dictionary creation and you get the dictionary from the other code?

Is there any way to serialize/deserialize it correctly with the comparer?

like image 631
Bug Avatar asked Jan 07 '14 16:01

Bug


People also ask

How do you serialize a Dictionary to a JSON string?

Simple One-Line Answer. This code will convert any Dictionary<Key,Value> to Dictionary<string,string> and then serialize it as a JSON string: var json = new JavaScriptSerializer(). Serialize(yourDictionary.

Is a Dictionary JSON serializable?

Json can't serialize Dictionary unless it has a string key. The built-in JSON serializer in . NET Core can't handle serializing a dictionary unless it has a string key.

Can we serialize Dictionary C#?

NET objects is made easy by using the various serializer classes that it provides. But serialization of a Dictionary object is not that easy. For this, you have to create a special Dictionary class which is able to serialize itself. The serialization technique might be different in different business cases.

Is JSON serialized or Deserialized?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object). If you serialize this result it will generate a text with the structure and the record returned.


2 Answers

One simple idea would be to create a subclass of Dictionary<string, string> that sets the comparer to StringComparer.OrdinalIgnoreCase by default, then deserialize into that instead of the normal dictionary. For example:

class CaseInsensitiveDictionary<V> : Dictionary<string, V>
{
    public CaseInsensitiveDictionary() : base(StringComparer.OrdinalIgnoreCase)
    {
    }
}

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        {
            ""Foo"" : 
            {
                ""fiZZ"" : 1,
                ""BUzz"" : ""yo""
            },
            ""BAR"" :
            {
                ""dIt"" : 3.14,
                ""DaH"" : true
            }
        }";

        var dict = JsonConvert.DeserializeObject<CaseInsensitiveDictionary<CaseInsensitiveDictionary<object>>>(json);

        Console.WriteLine(dict["foo"]["fizz"]);
        Console.WriteLine(dict["foo"]["buzz"]);
        Console.WriteLine(dict["bar"]["dit"]);
        Console.WriteLine(dict["bar"]["dah"]);
    }
}

Output:

1
yo
3.14
True
like image 165
Brian Rogers Avatar answered Sep 28 '22 06:09

Brian Rogers


It would be better to create a converter that would create the dictionary objects as needed. This is precisely what the Newtonsoft.Json.Converters.CustomCreationConverter<T> was designed for.

Here's one implementation that could create dictionaries that requires custom comparers.

public class CustomComparerDictionaryCreationConverter<T> : CustomCreationConverter<IDictionary>
{
    private IEqualityComparer<T> comparer;
    public CustomComparerDictionaryCreationConverter(IEqualityComparer<T> comparer)
    {
        if (comparer == null)
            throw new ArgumentNullException("comparer");
        this.comparer = comparer;
    }

    public override bool CanConvert(Type objectType)
    {
        return HasCompatibleInterface(objectType)
            && HasCompatibleConstructor(objectType);
    }

    private static bool HasCompatibleInterface(Type objectType)
    {
        return objectType.GetInterfaces()
            .Where(i => HasGenericTypeDefinition(i, typeof(IDictionary<,>)))
            .Where(i => typeof(T).IsAssignableFrom(i.GetGenericArguments().First()))
            .Any();
    }

    private static bool HasGenericTypeDefinition(Type objectType, Type typeDefinition)
    {
        return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeDefinition;
    }

    private static bool HasCompatibleConstructor(Type objectType)
    {
        return objectType.GetConstructor(new Type[] { typeof(IEqualityComparer<T>) }) != null;
    }

    public override IDictionary Create(Type objectType)
    {
        return Activator.CreateInstance(objectType, comparer) as IDictionary;
    }
}

Do note however that this converter will apply to all dictionary types where the key is covariant with T, regardless of value type.

Then to use it:

var converters = new JsonConverter[]
{
    new CustomComparerDictionaryCreationConverter<string>(StringComparer.OrdinalIgnoreCase),
};
var dict = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, object>>>(jsonString, converters);
like image 31
Jeff Mercado Avatar answered Sep 28 '22 08:09

Jeff Mercado