使用泛型

时间:2017-04-27 22:52:47

标签: c# json generics serialization

我创建了一个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。关于这一点的一点注意事项:DictionarygenericMethodCache.GetMethod(nameof(GenericMethodCache<int, int>.GetKeyFetcher))不喜欢nameof等通用类型定义。它只喜欢泛型类型GenericMethodCache<,>。但是,从长远来看,GenericMethodCache<int, int>将被忽略,并返回名称int, int

2 个答案:

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