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表达式等,不支持开放泛型参数,通过新增非泛型转换器工厂,运行时动态生成对应泛型类型的转换器实例即可解决问题,原有转换器和特性代码无需修改。
- 修改实体类的转换器特性,替换为工厂类型:
[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!; }
- 新增转换器工厂实现:
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); } }
- 调用示例:
// 定义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
相关产品推荐
相关产品推荐

