JSON.NET – CASE INSENSITIVE DICTIONARY

JSON.NET can of course deserialize a dictionary out of the box but yesterday I needed to change the way the dictionary is created during deserialization.

I needed the dictionary to be case insensitive when comparing string-keys, which means that the dictionary needs to be created with the StringComparer.OrdinalIgnoreCase option.

new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);  

I my case I needed to do this within a RavenDB context so I asked the great people at the RavenDB mailing list for any built in support.

I was pointed in few different directions and finally I decided that a I should implement a JsonConverter.

This is my implementation and I hope this could help anyone else that needs some of their dictionaries to be case insensitive.

using System;  
using System.Collections.Generic;  
using System.Linq;  
using log4net;  
using Raven.Imports.Newtonsoft.Json;  
using Raven.Imports.Newtonsoft.Json.Linq;

namespace MyNamespace.Raven.JsonConverters  
{
    public class CaseInsensitiveDictionaryConverter<T> : JsonConverter
    {
        private readonly ILog logger = LogManager.GetLogger(typeof (CaseInsensitiveDictionaryConverter<T>));

        public override bool CanWrite
        {
            get { return false; }
        }

        public override bool CanRead
        {
            get { return true; }
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotSupportedException();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            logger.DebugFormat("Reading JSON for: {0}", objectType.FullName);
            if (reader.TokenType == JsonToken.Null)
            {
                logger.Debug("Reader's token type is null");
                return null;
            }

            var jsonObject = JObject.Load(reader);
            var jsonString = jsonObject.ToString();
            logger.DebugFormat("JSON: {0}", jsonString);

            logger.Debug("Copying original Dictionary<> to new Dictionary<>(StringComparer.OrdinalIgnoreCase)");

            var originalDictionary = JsonConvert.DeserializeObject<Dictionary<string, T>>(jsonString);
            return originalDictionary == null ? null : new Dictionary<string, T>(originalDictionary, StringComparer.OrdinalIgnoreCase);
        }

        public override bool CanConvert(Type objectType)
        {
            return objectType.GetInterfaces().Count(i => HasGenericTypeDefinition(i, typeof(IDictionary<,>))) > 0;
        }

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

The converter is used like this:

namespace MyNamespace.Models.Data  
{
    public class MyClass
    {
        [JsonConverter(typeof(CaseInsensitiveDictionaryConverter<MyObject>))]
        public IDictionary<string, MyObject> MyDictionary { get; set; }
    }
}

Note: If you use this converter within a RavenDB context you must import JSON.NET from the Raven.Imports.Newtonsoft namespace. To use it “standalone” just use the standard JSON.NET namespaces.

comments powered by Disqus