Generic handling of types with null values ​​or non-empty values

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>>()));
} //                                             ^^^^ this null
// Then here append the items from jObject["Groupings"], and finally ToLookup.

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 {
   // 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 - Dictionary : ( ) , value plus , .

. , , .

P.S. : genericMethodCache.GetMethod(nameof(GenericMethodCache<int, int>.GetKeyFetcher)). nameof , GenericMethodCache<,>. GenericMethodCache<int, int>. , , int, int GenericMethodCache.

+4
2

(TKey)(object)null (TKey)null. , NullReferenceException, TKey - , , , -, , JSON ILookup TKey.

+1

, 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;
}
0

Source: https://habr.com/ru/post/1675863/


All Articles