У меня проблема с Json.NET. Мне нужно использовать разные общие JsonConverters для разных свойств в одном классе, который также является универсальным, когда типы свойств включают параметр универсального типа ...

0
Dmitriy 4 Янв 2021 в 01:41

1 ответ

Лучший ответ

Что вы хотите сделать, так это использовать общий параметр TTable в качестве универсального параметра для ваших пользовательских конвертеров JSON, например:

public class ExpresssionDTO<TTable> : BaseDTO where TTable : class
{
    [JsonProperty(ItemConverterType = typeof(PredicateSerializationConverter<TTable>))] // Here
    public ICollection<Expression<Func<TTable, bool>>> Predicates { get; set; } = new List<Expression<Func<TTable, bool>>>();

    [JsonConverter(converterType: typeof(FilterSerializationConverter<TTable>))] // And here
    public Expression<Func<TTable, object>> Filter { get; set; } = null;
}

Но вы не можете, потому что C # запрещает общие параметры в атрибутах. Похоже, вы надеетесь, что если вы укажете открытый универсальный тип для преобразователя, а тип родительского объекта также является универсальным типом с таким же количеством общих аргументов, то Json.NET автоматически построит преобразователь, подключив родительский тип общие аргументы - здесь TTable. К сожалению, это не реализовано.

Итак, каковы ваши варианты?

Во-первых , вы можете создать настраиваемый преобразователь контрактов. унаследованный от DefaultContractResolver, который создает и применяет соответствующие конкретные универсальные преобразователи. Поскольку вы применяете преобразователи к свойствам, вам необходимо переопределить JsonProperty.Converter или < a href = "https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_Serialization_JsonProperty_ItemConverter.htm" rel = "nofollow noreferrer"> JsonProperty.ItemConverter по мере необходимости.

Во-вторых, можно отказаться от подхода универсального преобразователя и создать неуниверсальные преобразователи для сериализации фильтров и предикатов. Вы можете сделать это, потому что, хотя использование универсальных шаблонов при написании преобразователей удобно и читаемо, это не является строго необходимым, поскольку вся необходимая информация о типе передается в неуниверсальные методы чтения и записи:

Ваш вопрос не содержит минимального воспроизводимого примера для вашего ExpressionSerializer<T, U>. Если вы можете легко переписать его так, чтобы он не был универсальным, вам следует подумать о том, чтобы сделать это. Если нет, вы можете принять шаблон декоратора и обернуть существующие универсальные преобразователи в декоратор, который выводит необходимые общие параметры из objectType или value, например:

public class GenericFuncExpressionArgumentConverterDecorator : JsonConverter
{
    readonly Type openGenericConverterType;
    volatile Tuple<Type, JsonConverter> converterCache;
            
    public GenericFuncExpressionArgumentConverterDecorator(Type openGenericConverterType)
    {
        if (openGenericConverterType == null)
            throw new ArgumentNullException();
        if (!openGenericConverterType.IsSubclassOf(typeof(JsonConverter)))
            throw new ArgumentException(string.Format("{0} is not a JsonConvreter", GetType().Name));
        if (!openGenericConverterType.IsGenericTypeDefinition)
            throw new ArgumentException(string.Format("{0} is not an open generic type", GetType().Name));
        this.openGenericConverterType = openGenericConverterType;
    }

    public override bool CanConvert(Type objectType) => 
        throw new NotImplementedException(string.Format("{0} is intended to be applied via a JsonConverter or JsonProperty attribute", GetType().Name));

    JsonConverter GetConverter(Type objectType)
    {
        var cache = converterCache;
        if (cache != null && cache.Item1 == objectType)
            return cache.Item2;
        // Despite the documentation, Expression<T> is not actually sealed in .Net 5!
        // https://github.com/dotnet/runtime/blob/master/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/LambdaExpression.cs#L174
        var expressionType = objectType.BaseTypesAndSelf().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Expression<>)).FirstOrDefault();
        if (expressionType == null)
            throw new JsonSerializationException(string.Format("Invalid expression type {0}", objectType));
        var delegateType = objectType.GetGenericArguments().Single();
        if (!delegateType.IsGenericType || delegateType.GetGenericTypeDefinition() != typeof(Func<,>))
            throw new JsonSerializationException(string.Format("Invalid delegate type {0}", delegateType));
        var argType = delegateType.GetGenericArguments()[0];
        var converterType = openGenericConverterType.MakeGenericType(new [] { argType });
        var converter = (JsonConverter)Activator.CreateInstance(converterType);
        converterCache = Tuple.Create(objectType, converter);
        return converter;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
        GetConverter(objectType).ReadJson(reader, objectType, existingValue, serializer);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) =>
        GetConverter(value.GetType()).WriteJson(writer, value, serializer);
}

public static class TypeExtensions
{
    public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
    {
        while (type != null)
        {
            yield return type;
            type = type.BaseType;
        }
    }
}

А затем примените его к своей модели следующим образом, передав открытые универсальные типы преобразователей в качестве аргументов преобразователя:

public class ExpresssionDTO<TTable> : BaseDTO where TTable : class
{
    [JsonProperty(ItemConverterType = typeof(GenericFuncExpressionArgumentConverterDecorator), ItemConverterParameters = new object [] { typeof(PredicateSerializationConverter<>) })]
    public ICollection<Expression<Func<TTable, bool>>> Predicates { get; set; } = new List<Expression<Func<TTable, bool>>>();

    [JsonConverter(typeof(GenericFuncExpressionArgumentConverterDecorator), new object [] { typeof(FilterSerializationConverter<>) })]
    public Expression<Func<TTable, object>> Filter { get; set; } = null;
}

Демо-скрипт здесь.

1
dbc 4 Янв 2021 в 20:27