【问题标题】:Json.NET Dictionary<string,T> with StringComparer serialization带有 StringComparer 序列化的 Json.NET Dictionary<string,T>
【发布时间】:2014-01-07 16:06:44
【问题描述】:

我有一本字典Dictionary&lt;string, Dictionary&lt;string, object&gt;&gt;。外部字典和内部字典都有一个相等比较器集(在我的例子中是StringComparer.OrdinalIgnoreCase)。在字典被序列化和反序列化后,两个字典的比较器都没有设置为StringComparer.OrdinalIgnoreCase

如果您可以控制代码中字典的创建,则可以创建一个从字典继承的类,并在该类的默认构造函数中设置比较器。但是,如果您无法控制字典的创建并且您从其他代码中获取字典怎么办?

有没有办法用比较器正确序列化/反序列化?

【问题讨论】:

    标签: c# json.net


    【解决方案1】:

    一个简单的想法是创建一个Dictionary&lt;string, string&gt; 的子类,默认情况下将比较器设置为StringComparer.OrdinalIgnoreCase,然后反序列化为它而不是普通字典。例如:

    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"]);
        }
    }
    

    输出:

    1
    yo
    3.14
    True
    

    【讨论】:

    • 好吧,我也想过同样的事情,但在某些情况下,您无法控制字典的创建。外部字典可以很容易地转换为不区分大小写的字典,但是要转换内部字典,对我来说将是很多开销。这就是为什么我正在寻找通用解决方案,而不是解决方法。
    • 也许您应该编辑您的问题以说明此要求并提供有关您的情况的更多详细信息。例如,您现在如何进行反序列化?什么在您的控制范围内,什么不在您的控制范围内?您是否有任何示例代码可以展示您正在做什么或试图做什么?您提供的详细信息越多,您收到的答案就越好。
    【解决方案2】:

    最好创建一个转换器来根据需要创建字典对象。这正是 Newtonsoft.Json.Converters.CustomCreationConverter&lt;T&gt; 的设计目的。

    这是一种可以创建需要自定义比较器的字典的实现。

    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;
        }
    }
    

    但请注意,此转换器将适用于键与 T 协变的所有字典类型,无论值类型如何。

    然后使用它:

    var converters = new JsonConverter[]
    {
        new CustomComparerDictionaryCreationConverter<string>(StringComparer.OrdinalIgnoreCase),
    };
    var dict = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, object>>>(jsonString, converters);
    

    【讨论】:

    • 很高兴看到一种干净的方法。 :-)
    【解决方案3】:

    创建一个扩展方法,它将值从区分大小写的字典复制到新的不区分大小写的字典中。

    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;
    }
    

    用法:

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

    虽然这对我有用(我喜欢这个解决方案,因为它很简单),但请注意以下注意事项:

    • 复制会产生少量开销
    • 如果字典中有重复的键(例如“cat”和“CAT”),则会覆盖其中的一个。在这种情况下,您可以轻松调整该方法以引发异常(如果需要)。
    • 我的解决方案在反序列化期间没有严格使用比较器,但很可能是让您的字典进入不区分大小写状态的最简单方法。

    【讨论】:

    • 不发表评论就投反对票?严重地?该解决方案按预期工作,性能可以接受,并且声明了使用它的注意事项。
    • 或者更好:return new Dictionary(caseSensitiveDictionary, StringComparer.OrdinalIgnoreCase);
    猜你喜欢
    • 1970-01-01
    • 2015-05-12
    • 2011-02-10
    • 1970-01-01
    • 1970-01-01
    • 2012-12-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多