如何在C#中使用System.Text.Json反序列化子数组数目可变的JSON数组

tjjdgumg  于 2022-12-20  发布在  C#
关注(0)|答案(1)|浏览(281)

是否可以自动将JSON反序列化为数组的数组,即使JSON有时只包含一个数组元素,因此在技术上不表示数组的数组,而是表示单个数组元素?
json是一个图表,应该有许多[x,y]点值,但每隔一段时间它只有一个点。
仅包含一个数组元素时的示例数据:

{"data" : { "chart": [ 1, 1 ] } }

当它包含一个数组的数组时,它通常看起来像这样:

{"data": {"chart": [ [ 1, 1 ],  [ 2, 2 ], [ 3, 3 ], [ 4, 4 ]]}}

我意识到我可以将其反序列化为:

public class Data
{
    public List<object> chart { get; set; }
}

public class Root
{
    public Data data { get; set; }
}

但我更希望有一个List<List<double>>,即使它只包含一个数组元素和外部List,显然只包含List/array和一个元素/点。

  • 这是否可以由一些自定义转换器通过System.Text.Json实现?
  • 它也适用于JSON中的多个图表数据,即如果我有“chart 1”,“chart 2”,“chart 3”等。

短暂性脑缺血发作
P.S.我无法访问JSON生产者的代码,不幸的是,它只在只有一个点时才能像描述的那样工作(所以不,我不能让它返回x,y而不是[x,y],如果这是有效的JSON的话。

xxls0lw8

xxls0lw81#

假设您的JSON数组始终只是一个2D或1D数组,您可以将您的c#属性声明为一个锯齿状2D列表List<List<T>>,并应用一个custom JsonConverter来检查传入的JSON是一个1D还是2D数组,如果不是2D数组,则反序列化一个List<T>并返回包含该列表的List<List<T>>
首先介绍以下转炉厂及转炉:

public class Jagged2DOr1DListConverter : JsonConverterFactory
{
    public virtual bool CanWrite => true;
    
    public override bool CanConvert(Type typeToConvert) => Get2DListItemType(typeToConvert) != null;

    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        var itemType = Get2DListItemType(typeToConvert)!;
        var converterType = typeof(Jagged2DOr1DListConverter<>).MakeGenericType(itemType);
        return (JsonConverter)Activator.CreateInstance(converterType, new object [] { CanWrite })!;
    }
    
    static Type? Get2DListItemType(Type type)
    {
        if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(List<>))
            return null;
        var midType = type.GetGenericArguments()[0];
        if (!midType.IsGenericType || midType.GetGenericTypeDefinition() != typeof(List<>))
            return null;
        var itemType = midType.GetGenericArguments()[0];
        if (itemType == typeof(string) || itemType == typeof(byte []))
            return itemType;
        // values declared as object are serialized polymorphically, so we can't say whether they will appear as JSON arrays, objects or primitives.  
        // So don't convert List<List<object>> automatically.
        if (itemType == typeof(object))
            return null; 
        // The following check could be enhanced to detect and allow conversion of List<List<Dictionary<TKey, TValue>>> types, since these are serialized as JSON objects rather than arrays.
        if (typeof(IEnumerable).IsAssignableFrom(itemType))
            return null;
        return itemType;
    }
}

public class Jagged2DOr1DListReadOnlyConverter : Jagged2DOr1DListConverter
{
    public override bool CanWrite => false;
}

public class Jagged2DOr1DListConverter<TItem> : JsonConverter<List<List<TItem>>>
{
    public Jagged2DOr1DListConverter() : this(true) {}
    public Jagged2DOr1DListConverter(bool canWrite) => CanWrite = canWrite;

    public bool CanWrite { get; }

    public override List<List<TItem>>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.Null)
            return null;
        else if (reader.TokenType != JsonTokenType.StartArray)
            throw new JsonException();
        List<List<TItem>> outerList = new();
        List<TItem>? innerList = null;

        while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
        {
            if (reader.TokenType == JsonTokenType.StartArray)
            {
                if (outerList.Count > 0 && innerList != null)
                    throw new JsonException();  // Mixtures of items and lists of items are not implemented
                var itemList = JsonSerializer.Deserialize<List<TItem>>(ref reader, options); // You could throw an exception here if itemList is null, if you want
                outerList.Add(itemList!); // You could throw an exception here if the 
            }
            else
            {
                if (innerList == null)
                {
                    if (outerList.Count > 0)
                        throw new JsonException();  // Mixtures of items and lists of items are not implemented
                    outerList.Add(innerList = new ());
                }
                innerList.Add(JsonSerializer.Deserialize<TItem>(ref reader, options)!);
            }
        }
        return outerList;
    }

    public override void Write(Utf8JsonWriter writer, List<List<TItem>> value, JsonSerializerOptions options)
    {
        if (CanWrite && value.Count == 1)
        {
            JsonSerializer.Serialize(writer, value.First(), options);
        }
        else
        {
            writer.WriteStartArray();
            foreach (var item in value)
                JsonSerializer.Serialize(writer, item, options);
            writer.WriteEndArray();
        }
    }
}

然后,修改Data如下:

public class Data
{
    public List<List<decimal>> chart { get; set; } = new (); // Use int, double, float or decimal here, as required.
}

现在,您可以通过将Jagged2DOr1DListConverter添加到JsonSerializerOptions来进行反序列化,如下所示:

var options = new JsonSerializerOptions
{
    Converters = { new Jagged2DOr1DListConverter() },
    // Other    
    WriteIndented = true,
};
var root = JsonSerializer.Deserialize<Root>(json, options);

或者,您可以将转换器直接应用于Data.chart,如下所示:

public class Data
{
    [JsonConverter(typeof(Jagged2DOr1DListConverter))]
    public List<List<decimal>> chart { get; set; } = new (); // Use int, double, float or decimal here, as required.
}

注:

  • 如果不希望将包含单个项目的2D列表重新序列化为1D JSON数组,请使用Jagged2DOr1DListReadOnlyConverter
  • Jagged2DOr1DListConverter将自动转换所有List<List<T>>类型,其中T本身不是可枚举类型。如果只希望转换特定类型,请改为将Jagged2DOr1DListConverter<TItem>的示例添加到JsonSerializerOptions.Converters
  • 如果不允许外部列表使用空值,请重写Jagged2DOr1DListConverter<TItem>.HandleNull,并在reader.TokenType == JsonTokenType.Null时从Read()引发异常。
  • 字典列表的列表不会被Jagged2DOr1DListConverter自动转换。如果需要,你可以在Get2DListItemType(Type type)中增强这个检查,为字典项返回一个非空的项类型:
// The following check could be enhanced to detect and allow conversion of List<List<Dictionary<TKey, TValue>>>` types, since these are serialized as JSON objects rather than arrays.
 if (typeof(IEnumerable).IsAssignableFrom(itemType))
     return null;
  • System.Text.Json以多态方式序列化声明为object的值,因此内部项本身可能被序列化为数组。

演示小提琴here

相关问题