System.Text.Json反序列化自定义只读结构体失败问题
问题根因
System.Text.Json 内置序列化器不会自动识别自定义只读结构体的静态实例映射、隐式字符串转换逻辑。未注册自定义转换器时,无法将JSON中的字符串值映射到对应结构体实例,就会触发类型转换异常。该方案完全保留原有Side结构体的只读设计,不需要将其修改为枚举类型。
实现步骤
1. 编写适配System.Text.Json的自定义转换器
对齐提供的Json.NET实现逻辑,支持精确匹配+大小写不敏感匹配、支持可空值类型、序列化自动输出对应字符串,先写泛型基类:
using System.Text.Json; using System.Text.Json.Serialization; public abstract class StjStringStructConverter<T> : JsonConverter<T> where T : struct { protected abstract IReadOnlyList<(T Instance, string JsonValue)> Mapping { get; } private readonly bool _enableCaseInsensitiveMatch; protected StjStringStructConverter(bool enableCaseInsensitiveMatch = true) { _enableCaseInsensitiveMatch = enableCaseInsensitiveMatch; } public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) return default; if (reader.TokenType != JsonTokenType.String) throw new JsonException($"Token type mismatch when parsing {typeof(T).Name}, expect string but get {reader.TokenType}"); var rawValue = reader.GetString(); if (string.IsNullOrWhiteSpace(rawValue)) return default; var matched = Mapping.FirstOrDefault(item => item.JsonValue.Equals(rawValue, StringComparison.Ordinal)); if (matched.Equals(default) && _enableCaseInsensitiveMatch) { matched = Mapping.FirstOrDefault(item => item.JsonValue.Equals(rawValue, StringComparison.OrdinalIgnoreCase)); } if (matched.Equals(default)) { Console.WriteLine($"[Warning] Failed to map {typeof(T).Name} value: {rawValue}, valid values: {string.Join(", ", Mapping.Select(i => i.JsonValue))}"); return default; } return matched.Instance; } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { var matched = Mapping.FirstOrDefault(item => item.Instance.Equals(value)); if (matched.Equals(default)) { writer.WriteNullValue(); return; } writer.WriteStringValue(matched.JsonValue); } public override bool HandleNull => true; }
再编写Side类型专属的转换器,配置映射关系:
public class SideJsonConverter : StjStringStructConverter<Side> { protected override IReadOnlyList<(Side Instance, string JsonValue)> Mapping => new List<(Side, string)> { (Side.Buy, "BUY"), (Side.Sell, "SELL") }; }
2. 注册转换器
两种方式二选一即可:
- 全局注册:所有使用
Side类型的序列化/反序列化场景自动生效
var serializerOptions = new JsonSerializerOptions { NumberHandling = JsonNumberHandling.AllowReadingFromString, Converters = { new SideJsonConverter() } }; // 反序列化时传入配置即可正常运行 var deserialized = JsonSerializer.Deserialize<BinanceStreamOrderUpdate>(json, serializerOptions);
- 属性标记:仅对指定属性生效,无需全局配置
public record BinanceStreamOrderUpdate { [JsonPropertyName("S")] [JsonConverter(typeof(SideJsonConverter))] public Side Side { get; init; } [JsonPropertyName("q")] public decimal Quantity { get; init; } }
效果说明
- 原有
Side只读结构体的代码完全不需要修改,不需要开放构造函数、不需要改成枚举、不需要调整现有隐式转换等逻辑 - 反序列化时JSON中的
"S":"BUY"/"S":"SELL"会正确映射到对应的静态Side实例,序列化时也会自动输出标准的大写字符串值 - 匹配逻辑和提供的Json.NET实现完全对齐,遇到未知值默认输出警告不中断流程,如果需要严格校验,把匹配失败分支的日志输出替换为抛出
JsonException即可
内容的提问来源于stack exchange,提问作者nop
相关产品推荐
相关产品推荐

