You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#使用System.Text.Json反序列化按索引映射的泛型JSON数组

System.Text.Json 实现位置敏感数组反序列化方案

使用Json.NET可便捷实现JSON数组的反序列化,以下为使用System.Text.Json实现等效能力的方案,支持泛型映射、从字符串读取数值。

待解析JSON结构

[340,{"a":["21040.00000",0,"0.00500000"],"b":["21037.70000",0,"0.49900000"],"c":["21039.00000","0.06009660"],"v":["660.49276224","3641.23932460"],"p":["20783.06665","20853.16080"],"t":[6207,22883],"l":["20500.10000","20500.10000"],"h":["21052.40000","21528.70000"],"o":["20716.30000","21416.30000"]},"ticker","XBT/USD"]

原有Json.NET实现方案

[JsonConverter(typeof(ArrayConverter))]
public class KrakenSocketEvent<T>
{
    [ArrayProperty(0)]
    public int ChannelId { get; set; }

    [ArrayProperty(1)]
    [JsonConversion]
    public T Data { get; set; } = default!;

    [ArrayProperty(2)]
    public string Topic { get; set; } = string.Empty;

    [ArrayProperty(3)]
    public string Symbol { get; set; } = string.Empty;
}

现有实现问题

泛型类型上直接标注泛型转换器特性时会触发编译错误:特性参数不能使用类型参数,需要在保留按数组索引映射属性、支持从字符串读取数值能力的前提下,解决泛型兼容问题。

报错的初始代码

实体定义:

[JsonConverter(typeof(JsonNumberHandlingPlainArrayConverter<KrakenSocketEvent<T>>))] // 编译报错位置
public record KrakenSocketEvent<T> where T : new()
{
    [JsonPlainArrayIndex(0)]
    public int ChannelId { get; init; }

    [JsonPlainArrayIndex(1)]
    public T Data { get; init; } = default!;

    [JsonPlainArrayIndex(2)]
    public string Topic { get; init; } = null!;

    [JsonPlainArrayIndex(3)]
    public string Symbol { get; init; } = null!;
}

自定义转换器实现:

public sealed class JsonNumberHandlingPlainArrayConverter<T> : JsonPlainArrayConverter<T> where T : new()
{
    protected override JsonSerializerOptions CustomizePropertyOptions(PropertyInfo info, JsonSerializerOptions options)
    {
        return new JsonSerializerOptions(options)
        {
            NumberHandling = JsonNumberHandling.AllowReadingFromString
        };
    }
}

[AttributeUsage(AttributeTargets.Property)]
public sealed class JsonPlainArrayIndexAttribute : Attribute
{
    public JsonPlainArrayIndexAttribute(int index)
    {
        Index = index;
    }

    public int Index { get; }
}

public class JsonPlainArrayConverter<T> : JsonConverter<T> where T : new()
{
    protected virtual JsonSerializerOptions CustomizePropertyOptions(PropertyInfo info, JsonSerializerOptions options)
    {
        return options;
    }

    public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        Debug.Assert(typeof(T) == typeToConvert);

        var props = typeToConvert.GetProperties();
        var linq = from prop in props
            let attr = prop.GetCustomAttributes(typeof(JsonPlainArrayIndexAttribute), true)
            where prop.CanWrite && attr.Length is 1
            orderby ((JsonPlainArrayIndexAttribute)attr[0]).Index
            select prop;

        var arr = JsonSerializer.Deserialize<IEnumerable<JsonElement>>(ref reader, options);
        if (arr is null)
        {
            return default;
        }

        var result = new T();
        foreach (var (prop, value) in linq.Zip(arr))
        {
            prop.SetValue(result, value.Deserialize(prop.PropertyType, CustomizePropertyOptions(prop, options)));
        }

        return result;
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        var type = typeof(T);
        var props = type.GetProperties();
        var linq = from prop in props
            let attr = prop.GetCustomAttributes(typeof(JsonPlainArrayIndexAttribute), true)
            where prop.CanRead && attr.Length is 1
            orderby ((JsonPlainArrayIndexAttribute)attr[0]).Index
            select prop.GetValue(value);
        JsonSerializer.Serialize<IEnumerable<object>>(writer, linq, options);
    }
}

修复方案

C#特性参数仅支持常量、封闭泛型类型、typeof表达式等,不支持开放泛型参数,通过新增非泛型转换器工厂,运行时动态生成对应泛型类型的转换器实例即可解决问题,原有转换器和特性代码无需修改。

  1. 修改实体类的转换器特性,替换为工厂类型:
[JsonConverter(typeof(JsonNumberHandlingPlainArrayConverterFactory))]
public record KrakenSocketEvent<T> where T : new()
{
    [JsonPlainArrayIndex(0)]
    public int ChannelId { get; init; }

    [JsonPlainArrayIndex(1)]
    public T Data { get; init; } = default!;

    [JsonPlainArrayIndex(2)]
    public string Topic { get; init; } = null!;

    [JsonPlainArrayIndex(3)]
    public string Symbol { get; init; } = null!;
}
  1. 新增转换器工厂实现:
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

public sealed class JsonNumberHandlingPlainArrayConverterFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        // 校验规则可按需调整,示例仅匹配KrakenSocketEvent<>泛型类型,且泛型参数有无参构造
        return typeToConvert.IsGenericType
               && typeToConvert.GetGenericTypeDefinition() == typeof(KrakenSocketEvent<>)
               && typeToConvert.GetGenericArguments()[0].GetConstructor(BindingFlags.Public | BindingFlags.Instance, Type.EmptyTypes) != null;
    }

    public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        // 运行时构造对应封闭泛型类型的转换器实例
        Type converterClosedType = typeof(JsonNumberHandlingPlainArrayConverter<>).MakeGenericType(typeToConvert);
        return (JsonConverter?)Activator.CreateInstance(converterClosedType);
    }
}
  1. 调用示例:
// 定义Ticker数据结构匹配JSON数组第二个位置的对象
public class TickerData
{
    public string[] a { get; set; } = Array.Empty<string>();
    public string[] b { get; set; } = Array.Empty<string>();
    public string[] c { get; set; } = Array.Empty<string>();
    public string[] v { get; set; } = Array.Empty<string>();
    public string[] p { get; set; } = Array.Empty<string>();
    public long[] t { get; set; } = Array.Empty<long>();
    public string[] l { get; set; } = Array.Empty<string>();
    public string[] h { get; set; } = Array.Empty<string>();
    public string[] o { get; set; } = Array.Empty<string>();
}

// 反序列化
var json = "[340,{\"a\":[\"21040.00000\",0,\"0.00500000\"],\"b\":[\"21037.70000\",0,\"0.49900000\"],\"c\":[\"21039.00000\",\"0.06009660\"],\"v\":[\"660.49276224\",\"3641.23932460\"],\"p\":[\"20783.06665\",\"20853.16080\"],\"t\":[6207,22883],\"l\":[\"20500.10000\",\"20500.10000\"],\"h\":[\"21052.40000\",\"21528.70000\"],\"o\":[\"20716.30000\",\"21416.30000\"]},\"ticker\",\"XBT/USD\"]";
var result = JsonSerializer.Deserialize<KrakenSocketEvent<TickerData>>(json);

内容的提问来源于stack exchange,提问作者nop

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 13:06:24