反序列化锯齿状数组时如何应用 ItemConverterType?

时间:2021-04-22 17:24:14

标签: c# json.net deserialization

我有正确使用我的“InternedString”转换器的现有属性:

[JsonProperty(ItemConverterType=typeof(InternedString))]
public string[] prizeTypes;

是否有一种简单的方法可以将其应用于 jagged 二维字符串数组,而无需编写自己的 string[][] 转换器?

[JsonProperty(ItemConverterType=typeof(InternedString))]
public string[][] prizeTypesByRoomType;

(它给了我一个“无法将数组转换为字符串”的异常,这是显而易见的,但我有点希望它可以递归地应用该类型)

1 个答案:

答案 0 :(得分:2)

Json.NET 没有使用属性将自定义 JsonConverter 应用于交错数组(例如 string [][])的项目的功能。 ItemConverterType 仅适用于最外层数组的项。

您可以做的是应用 decorator pattern 来创建一个 custom JsonConverter,该 How to apply a custom JsonConverter to the values inside a list inside a dictionary? 为任意深度的锯齿状数组的最内部项目包装了一些内部项目转换器。

首先定义如下转换器:

public class JaggedArrayItemConverterDecorator : JsonConverter
{
    readonly JsonConverter itemConverter;

    public JaggedArrayItemConverterDecorator(Type type) => 
        itemConverter = (JsonConverter)Activator.CreateInstance(type ?? throw new ArgumentNullException());

    public override bool CanConvert(Type objectType) => objectType.IsJaggedRankOneArray(out var itemType) && itemConverter.CanConvert(itemType);

    public override bool CanRead => itemConverter.CanRead;
    public override bool CanWrite => itemConverter.CanWrite;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        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 itemType = objectType.GetElementType();
        IList list = (IList)serializer.ContractResolver.ResolveContract(typeof(List<>).MakeGenericType(itemType)).DefaultCreator();
        while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndArray)
        {
            if (itemType.IsArray)
                list.Add(ReadJson(reader, itemType, null, serializer));
            else
                list.Add(itemConverter.ReadJson(reader, itemType, null, serializer));
        }
        var array = Array.CreateInstance(itemType, list.Count);
        list.CopyTo(array, 0);
        return array;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var itemType = value.GetType().GetElementType();
        writer.WriteStartArray();
        foreach (var item in (IList)value)
        {
            if (item == null)
                writer.WriteNull();
            else if (itemType.IsArray)
                WriteJson(writer, item, serializer);
            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 bool IsJaggedRankOneArray(this Type type, out Type innermostItemType)
    {
        innermostItemType = null;
        var currentType = type ?? throw new ArgumentNullException(nameof(type));
        while (currentType.IsArray)
        {
            if (currentType.GetArrayRank() != 1)
                return false;
            currentType = currentType.GetElementType();
        }
        if (currentType != type)
        {
            innermostItemType = currentType;
            return true;
        }
        return false;
    }
}

然后您可以将其应用于模型中任意深度的锯齿状数组,如下所示:

public class Model
{
    [JsonProperty(ItemConverterType=typeof(InternedString))] // For 1d arrays you can use either ItemConverterType=typeof(InternedString) or JaggedArrayItemConverterDecorator
    public string[] prizeTypes { get; set; }

    [JsonConverter(typeof(JaggedArrayItemConverterDecorator), typeof(InternedString))]
    public string[] prizeTypesByRoomType { get; set; }

    [JsonConverter(typeof(JaggedArrayItemConverterDecorator), typeof(InternedString))]
    public string[][] prizeTypesByRoomType2d { get; set; }

    [JsonConverter(typeof(JaggedArrayItemConverterDecorator), typeof(InternedString))]
    public string[][][] prizeTypesByRoomType3d { get; set; }

    [JsonConverter(typeof(JaggedArrayItemConverterDecorator), typeof(InternedString))]
    public string[][][][] prizeTypesByRoomType4d { get; set; }
    
    [JsonProperty(ItemConverterType=typeof(InternedString))] // ItemConverterType works for multidimensional arrays
    public string[,] prizeTypesMultidimensional { get; set; }
}

注意事项:

  • 转换器是为数组而不是列表实现的。如果您需要支持锯齿状列表,请参阅here

  • 转换器未针对多维数组(例如 string [,])或包含多维数组的锯齿状数组实现。但是,ItemConverterType 实际上支持多维数组,因此除非您有多维数组的锯齿状数组,反之亦然,否则您应该全部设置。

演示小提琴{{3}}。

相关问题