如何将自定义 JsonConverter 应用于字典中列表中的值?

问题描述

我有一个 CustomConverter : JsonConverter<int> 用于整数,我需要向 [JsonConverter(typeof(CustomConverter))] 属性添加一个 Dictionary<string,List<int>> 属性。将自定义转换器应用于 intListDictionary 工作正常:

public class Example 
{
    [JsonConverter(typeof(CustomConverter))]
    public int ExampleInt { get; set; }
    [JsonProperty(ItemConverterType = typeof(CustomConverter))]
    public List<int> ExampleList { get; set; }
    
    // How do I specify the Converter attribute for the int in the following line?
    public Dictionary<string,List<int>> ExampleDictionary { get; set; }
}

但是我不知道如何指定 CustomConverter 应该用于 int 内的 List 内的 Dictionary 值。我该怎么做?

解决方法

Dictionary<string,List<int>> 是集合的嵌套集合,您正在寻找类似 ItemOfItemsConverterType 的东西,对应于 ItemConverterType,为集合的项目的项目指定转换器。不幸的是,没有实现这样的属性。相反,有必要为嵌套的 List<int> 集合创建一个转换器,该集合调用所需的最内层项目转换器。

这可以通过为 JsonConverter 实现以下 List<> decorator 来实现:

public class ListItemConverterDecorator : JsonConverter
{
    readonly JsonConverter itemConverter;
    
    public ListItemConverterDecorator(Type type) => 
        itemConverter = (JsonConverter)Activator.CreateInstance(type ?? throw new ArgumentNullException());

    public override bool CanConvert(Type objectType) =>
        !objectType.IsPrimitive && objectType != typeof(string) && objectType.BaseTypesAndSelf().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>));
    
    public override bool CanRead => itemConverter.CanRead;
    public override bool CanWrite => itemConverter.CanWrite;
    
    public override object ReadJson(JsonReader reader,Type objectType,object existingValue,JsonSerializer serializer)
    {
        var itemType = objectType.BaseTypesAndSelf().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>)).Select(t => t.GetGenericArguments()[0]).First();
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        if (reader.TokenType != JsonToken.StartArray)
            throw new JsonSerializationException(string.Format("Unexpected token {0},expected {1}",reader.TokenType,JsonToken.StartArray));
        var list = existingValue as IList ?? (IList)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndArray)
            list.Add(itemConverter.ReadJson(reader,itemType,null,serializer));
        return list;
    }
    
    public override void WriteJson(JsonWriter writer,object value,JsonSerializer serializer)
    {
        writer.WriteStartArray();
        foreach (var item in (IList)value)
            if (item == null)
                writer.WriteNull();
            else
                itemConverter.WriteJson(writer,item,serializer);
        writer.WriteEndArray();
    }
}

public static partial class JsonExtensions
{
    public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
        reader.ReadAndAssert().MoveToContentAndAssert();

    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

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

然后按如下方式注释您的 Example 类,使用 JsonPropertyAttribute.ItemConverterParameters 指定内部项转换器 CustomConverter

public class Example 
{
    [JsonConverter(typeof(CustomConverter))]
    public int ExampleInt { get; set; }
    [JsonProperty(ItemConverterType = typeof(CustomConverter))]
    public List<int> ExampleList { get; set; }
    
    [JsonProperty(ItemConverterType = typeof(ListItemConverterDecorator),ItemConverterParameters = new object [] { typeof(CustomConverter) })]
    public Dictionary<string,List<int>> ExampleDictionary { get; set; }
}

现在你应该准备好了。演示小提琴here

,

为了实现这个功能,你需要创建 CustomClass 并继承它 IDictionary

public class CustomClass : IDictionary<string,List<int>>

然后你可以像下面这样使用这个类:

public class Example {
   [JsonConverter(typeof(CustomConverter)]
   public string ExampleString { get; set; }
   [JsonProperty(ItemConverterType = typeof(CustomConverter))]
   public List<int> ExampleList { get; set; }
   [JsonProperty(ItemConverterType = typeof(CustomConverter))]
   public CustomClass data { get; set; }
}

告诉我您需要任何其他信息。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...