我创建了一个LookupConverter : JsonConverter
类来执行ILookup
对象的JSON序列化和反序列化。正如您可能想象的那样,它有一些复杂性,必须处理泛型,并且由于缺乏公共具体的Lookup
类。为了提高性能,它将特定于类型的反射工作缓存在静态泛型类中。它完美无缺!
嗯,几乎完美。我今天才意识到它无法处理序列化包含空ILookup
的{{1}}。经过一些思考并意识到在JSON中,没有简单的方法来表示对象中的空键(因为每个键都被转换为字符串),我想我只是让输出对象变得有点大。
如果前一个输出是Key
,那么我认为新输出看起来像{"key1":[1,2,3]}
。这很尴尬,但到目前为止还不错。或者它可以是{Groupings:{"key1":[1,2,3]},NullKeyValue:[4,5,6]}
。无论如何都没什么大不了的。
为此添加序列化很简单。
然而,当反序列化时,我遇到了问题。我以前的反序列化程序非常简单(这里有一些复杂的缓存,请仔细查看过去,看看我的函数需要[{"key":"key1","values":[1,2,3]},{"key":null,"values":[4,5,6]}]
和jObject
并返回一个对象正确的类型,类似于serializer
lookupmaker(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);
List
s,如果有一个空键,则添加一个额外的值,然后抛出就像上面一样KeyValuePair
:
ToLookup
但是现在我在上面的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>>()));
} // ^^^^ this null
// Then here append the items from jObject["Groupings"], and finally ToLookup.
中收到错误:
参数类型&#39; null&#39;不能分配给参数类型&#39; TKey&#39;。
嗯,当然不是。它无法保证Add
不是非可空值类型。大。我只是在我的静态类TKey
上抛出一个约束where TKey : class
...只是,当我想要一个GenericMethodCache<TKey, TValue>
版本时,我遇到了麻烦,因为: struct
的重点是防止使用GenericMethodCache
的序列化程序代码处理泛型部分。我无法获得自动分辨率,因为分辨率无法使用类型约束来区分方法组。突然间,这种情况的复杂性爆发了,我不确定继续深入丛林试图让它正常工作是有道理的,所以我正在寻求指导!
由于这是一个非常复杂的场景,所以这里没有处理空键的完整代码(接下来是关于FunctionResultCache的更多内容):
object
public sealed class LookupConverter : JsonConverter {
// ReSharper disable once CollectionNeverUpdated.Local
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) =>
// ReSharper disable once AccessToStaticMemberViaDerivedType
_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);
}
}
}
// ReSharper disable once CollectionNeverUpdated.Local
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]);
}
);
// ReSharper disable once CollectionNeverUpdated.Local
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
,当你索引到一个不存在的键时,它会运行一个函数(在构造函数中传递)来获取值,然后是商店&amp;缓存值plus并将其返回给您,因此下次索引相同的键时,它将返回缓存的值。
我很抱歉这个问题的长度和代码。这是一个有点复杂的场景,为了获得有用的反馈,我必须展示一些有关正在发生的事情的细节。
P.S。关于这一点的一点注意事项:Dictionary
。 genericMethodCache.GetMethod(nameof(GenericMethodCache<int, int>.GetKeyFetcher))
不喜欢nameof
等通用类型定义。它只喜欢泛型类型GenericMethodCache<,>
。但是,从长远来看,GenericMethodCache<int, int>
将被忽略,并返回名称int, int
。
答案 0 :(得分:1)
您可以(TKey)(object)null
代替(TKey)null
。当然,如果NullReferenceException
是一个不可为空的值类型,这将抛出TKey
,但这似乎是有意义的,因为有问题的JSON不能反序列化为ILookup
指定的TKey
。
答案 1 :(得分:0)
我已将所提供的反馈纳入其中,包括使用key:(key)
和values:(values)
个对象的数组,以下是更改。有趣的是,我甚至不再有原始问题了!这通过了几次单元测试。
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteStartArray();
var groupings = (IEnumerable) value;
var getKey = _keyFetcherForType[value.GetType()];
foreach (dynamic grouping in groupings) {
writer.WriteStartObject();
writer.WritePropertyName("key");
object key = getKey(grouping);
if (key == null) {
writer.WriteNull();
} else {
serializer.Serialize(writer, key);
}
writer.WritePropertyName("values");
serializer.Serialize(writer, (IEnumerable) grouping);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
// -- snip --- //
private static class GenericMethodCache<TKey, TValue> {
public static Func<JArray, JsonSerializer, object> GetLookupMaker() =>
(jArray, serializer) =>
jArray
.Children()
.Select(jObject => new {
Key = jObject["key"].ToObject<TKey>(),
Values = jObject["values"].ToObject<List<TValue>>()
})
.SelectMany(
kvp => kvp.Values,
(kvp, value) => new KeyValuePair<TKey, TValue>(kvp.Key, value)
)
.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
public static Func<object, object> GetKeyFetcher() =>
grouping => ((IGrouping<TKey, TValue>) grouping).Key;
}