问题
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?
回答1:
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
回答2:
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);
回答3:
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.
来源:https://stackoverflow.com/questions/20976338/json-net-dictionarystring-t-with-stringcomparer-serialization