当属性类型包含泛型类型时,如何将泛型 JsonConverter 应用于泛型类的属性?

时间:2021-01-03 22:41:03

标签: c# .net-core json.net

我对 Json.NET 有问题。当属性类型包含类本身的泛型类型参数时,我需要对一个也是泛型的类中的不同属性使用不同的泛型 JsonConverters。

让我们有以下泛型类,其中参数 TTable 被一些需要转换的属性使用:

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

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

使用转换器:

public class PredicateSerializationConverter<TTable> : ExpressionSerializer<TTable, bool> where TTable : class
{
    public PredicateSerializationConverter() :base()
    {
    }
}

public class FilterSerializationConverter<TTable> : ExpressionSerializer<TTable, object> where TTable : class
{
    public FilterSerializationConverter() : base()
    {
    }
}

public class ExpressionSerializer<T, U> : JsonConverter where T : class     
{
    ...
}

在我的合同解析器中,我有一个错误:

Cannot create an instance of WebFoundationClassesCore.ServiceClasses.Converters.PredicateSerializationConverter`1[TTable] because Type.ContainsGenericParameters is true.

DefaultContractResolver 也出现了这个问题。

有什么办法可以解决我的问题吗?

1 个答案:

答案 0 :(得分: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# forbids generic parameters in attributes。您似乎希望,如果您为转换器指定一个开放的泛型类型,并且父对象类型也是具有相同数量的泛型参数的泛型类型,那么 Json.NET 将通过插入父对象的类型来自动构造转换器通用参数——这里是 TTable。不幸的是,这并没有实现。

那么,您有什么选择?

首先,您可以创建一个继承自 custom contract resolverDefaultContractResolver,以构建和应用适当的具体通用转换器。由于您将转换器应用于属性,因此您需要覆盖 DefaultContractResolver.CreateProperty 并根据需要设置 JsonProperty.ConverterJsonProperty.ItemConverter

其次,您可以放弃通用转换器方法并创建非通用转换器来序列化过滤器和谓词。您可以这样做,因为虽然在编写转换器时使用泛型方便且可读,但并非绝对必要,因为所有必需的类型信息都传递到非泛型读写方法中:

您的问题没有为您的 ExpressionSerializer<T, U> 显示 minimal reproducible example。如果您可以轻松地将其重写为非泛型,那么您应该考虑这样做。如果没有,您可以采用 decorator pattern 并将现有的通用转换器包装在一个装饰器中,该装饰器从 objectTypevalue 推断所需的通用参数,如下所示:

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

演示小提琴here

相关问题