Json.NET Dictionary with StringComparer serialization

后端 未结 3 1834
忘掉有多难
忘掉有多难 2020-12-16 11:30

I have a dictionary Dictionary>. Both the outer dictionary and the inner one have an equality comparer set(in my

相关标签:
3条回答
  • 2020-12-16 12:07

    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
    
    0 讨论(0)
  • 2020-12-16 12:07

    Create an extension method which will copy the values from your case-sensitive dictionary to a new case-insensitive dictionary.

    public static Dictionary<string, T> ToCaseInsensitive<T>(this Dictionary<string, T> caseSensitiveDictionary)
    {
        var caseInsensitiveDictionary = new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
        caseSensitiveDictionary.Keys.ToList()
            .ForEach(k => caseInsensitiveDictionary[k] = caseSensitiveDictionary[k]);
    
        return caseInsensitiveDictionary;
    }
    

    Usage:

    var newDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(value)
    .ToCaseInsensitive();
    

    Although this works for me (and I like this solution due to its simplicity), please note the following caveats:

    • There is a minor overhead incurred ito the copying
    • If you have duplicate keys in the dictionary (such as "cat" and "CAT"), one will be overwritten. You can easily adapt the method to throw an exception in such cases (if you want).
    • My solution does not strictly use the comparer during deserialization, but is most probably the easiest way to get your dictionary into a case-insensitive state.
    0 讨论(0)
  • 2020-12-16 12:33

    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);
    
    0 讨论(0)
提交回复
热议问题