I created a class LookupConverter : JsonConverterto do JSON serialization and object deserialization ILookup. As you can imagine, it has a certain complexity in dealing with generics and due to the lack of a public concrete class Lookup. For performance, it caches its type reflection work in a static generic class. It works great!
Well, almost excellent. I just realized today that it cannot handle serialization ILookup, which contains null Key. After some reflection and understanding that there is no easy way in JSON to represent a null key in an object (since each key is converted to a string), I thought I would just make the output object a bit big.
If the first output was, for example, {"key1":[1,2,3]}I realized that the new output might look like {Groupings:{"key1":[1,2,3]},NullKeyValue:[4,5,6]}. This is inconvenient, but so far so good. Or it could be [{"key":"key1","values":[1,2,3]},{"key":null,"values":[4,5,6]}]. Nothing wrong.
Adding serialization for this was cinch.
However, when the time came for deserialization, I had a problem. My former deserializer was very easy (here there is a complex caching, try to also view it and just see what my function takes jObject, and serializer, and returns an object of the correct type, which is used aslookupmaker(JObject.Load(reader), serializer);
public static Func<JObject, JsonSerializer, object> GetLookupMaker() =>
(jObject, serializer) => ((IEnumerable<KeyValuePair<string, JToken>>) jObject)
.SelectMany(
kvp => kvp.Value.ToObject<List<TValue>>(),
(kvp, value) => new KeyValuePair<TKey, TValue>(Convert<TKey>(kvp.Key), value)
)
.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
Ok, now I think I’ll just make Listfrom KeyValuePairs, add an extra value if there is a null key, and then click on it ToLookup, as above:
var list = new List<KeyValuePair<TKey, List<TValue>>>();
var nullKeyValue = jObject["NullKeyValue"];
if (nullKeyValue != null) {
list.Add(new KeyValuePair<TKey, List<TValue>>(null, nullKeyValue.ToObject<List<TValue>>()));
}
But now I get the error in the above Add:
The argument type "null" is not assigned to the parameter type "TKey".
, , . , TKey , NULL. . where TKey : class GenericMethodCache<TKey, TValue>... , : struct, , GenericMethodCache , object . , . , , , , , !
, , ( FunctionResultCache next):
public sealed class LookupConverter : JsonConverter {
private static readonly FunctionResultCache<Type, bool> s_typeCanConvertDictionary =
new FunctionResultCache<Type, bool>(type =>
new [] { type }
.Concat(type.GetInterfaces())
.Any(iface => iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(ILookup<,>))
);
public override bool CanConvert(Type objectType) => s_typeCanConvertDictionary[objectType];
public override bool CanWrite => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteStartObject();
var groupings = (IEnumerable) value;
var getKey = _keyFetcherForType[value.GetType()];
foreach (dynamic grouping in groupings) {
writer.WritePropertyName(getKey(grouping).ToString());
serializer.Serialize(writer, (IEnumerable) grouping);
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
_deserializerForType[objectType](JObject.Load(reader), serializer);
private static class GenericMethodCache<TKey, TValue> {
public static Func<JObject, JsonSerializer, object> GetLookupMaker() =>
(jObject, serializer) => ((IEnumerable<KeyValuePair<string, JToken>>) jObject)
.SelectMany(
kvp => kvp.Value.ToObject<List<TValue>>(),
(kvp, value) => new KeyValuePair<TKey, TValue>(Convert<TKey>(kvp.Key), value)
)
.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
public static Func<object, object> GetKeyFetcher() =>
grouping => ((IGrouping<TKey, TValue>) grouping)
.Key;
private static T Convert<T>(string input) {
try {
return (T) TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(input);
}
catch (NotSupportedException) {
return default(T);
}
}
}
private readonly FunctionResultCache<Type, Func<JObject, JsonSerializer, object>> _deserializerForType =
new FunctionResultCache<Type, Func<JObject, JsonSerializer, object>>(type => {
var genericMethodCache = typeof(GenericMethodCache<,>).MakeGenericType(type.GetGenericArguments());
return (Func<JObject, JsonSerializer, object>) genericMethodCache.GetMethod(nameof(GenericMethodCache<int, int>.GetLookupMaker)).Invoke(null, new object[0]);
}
);
private readonly FunctionResultCache<Type, Func<object, object>> _keyFetcherForType =
new FunctionResultCache<Type, Func<object, object>>(type => {
var genericMethodCache = typeof(GenericMethodCache<,>).MakeGenericType(type.GetGenericArguments());
return (Func<object, object>) genericMethodCache.GetMethod(nameof(GenericMethodCache<int, int>.GetKeyFetcher)).Invoke(null, new object[0]);
}
);
}
FunctionResultCache - Dictionary : ( ) , value plus , .
. , , .
P.S. : genericMethodCache.GetMethod(nameof(GenericMethodCache<int, int>.GetKeyFetcher)). nameof , GenericMethodCache<,>. GenericMethodCache<int, int>. , , int, int GenericMethodCache.