我有一个字典Dictionary<string, Dictionary<string, object>>
.外部字典和内部字典都有一个相等比较器集(在我的情况下是StringComparer.OrdinalIgnoreCase
)。序列化和反序列化字典后,两个字典的比较器未设置为 StringComparer.OrdinalIgnoreCase
。
如果可以控制代码中字典的创建,则可以创建从字典继承的类,并在该类的默认构造函数中设置比较器。但是,如果您无法控制字典的创建,并且从其他代码中获取字典怎么办?
有没有办法用比较器正确序列化/反序列化它?
想法是创建一个Dictionary<string, string>
子类,默认情况下将比较器设置为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
最好创建一个转换器,根据需要创建字典对象。 这正是Newtonsoft.Json.Converters.CustomCreationConverter<T>
的设计目的。
下面是一个可以创建需要自定义比较器的字典的实现。
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);
创建一个扩展方法,该方法将值从区分大小写的字典复制到新的不区分大小写的字典中。
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"),则其中一个将被覆盖。 在这种情况下,您可以轻松地调整方法以引发异常(如果需要)。
- 我的解决方案在反序列化期间没有严格使用比较器,但很可能是使字典进入不区分大小写状态的最简单方法。