如何使用System.Text.Json将动态键值JSON反序列化至指定自定义类结构
System.Text.Json 实现动态键JSON映射的方案
核心目标是将warnings节点下的动态字符串键(数字形式的区域ID)、对应数组值的结构,映射到已定义的三层类结构,全程无侵入修改原有业务类。
方案1:自定义原生JsonConverter(双向兼容,可复用)
System.Text.Json 对非标准结构映射的核心能力是自定义转换器,基于高性能的Utf8JsonReader/Utf8JsonWriter实现,对原有Root/WarningArea/Warning类无需做任何修改。
首先实现针对Root类型的自定义转换器:
using System.Text.Json; using System.Text.Json.Serialization; public class RootJsonConverter : JsonConverter<Root> { public override Root Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var warningAreas = new List<WarningArea>(); while (reader.Read()) { // 遍历到最外层对象结尾时退出 if (reader.TokenType == JsonTokenType.EndObject) break; // 定位到warnings节点 if (reader.TokenType == JsonTokenType.PropertyName && reader.GetString() == "warnings") { reader.Read(); if (reader.TokenType != JsonTokenType.StartObject) continue; // 遍历warnings下的所有动态属性 while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) break; if (reader.TokenType == JsonTokenType.PropertyName) { string areaIdStr = reader.GetString(); reader.Read(); // 反序列化当前键对应的Warning数组 if (reader.TokenType == JsonTokenType.StartArray) { var warnings = JsonSerializer.Deserialize<List<Warning>>(ref reader, options); // 容错转换ID,非数字键自动跳过 if (int.TryParse(areaIdStr, out int areaId)) { warningAreas.Add(new WarningArea { AreaId = areaId, Warnings = warnings ?? new List<Warning>() }); } } } } } } return new Root { WarningAreas = warningAreas }; } public override void Write(Utf8JsonWriter writer, Root value, JsonSerializerOptions options) { // 配套实现序列化逻辑,自动还原为原JSON结构 writer.WriteStartObject(); writer.WritePropertyName("warnings"); writer.WriteStartObject(); foreach (var area in value.WarningAreas) { writer.WritePropertyName(area.AreaId.ToString()); JsonSerializer.Serialize(writer, area.Warnings, options); } writer.WriteEndObject(); writer.WriteEndObject(); } }
注册转换器后直接使用即可:
var jsonOptions = new JsonSerializerOptions { Converters = { new RootJsonConverter() }, // 按需添加其他配置 PropertyNameCaseInsensitive = true }; // 反序列化 var result = JsonSerializer.Deserialize<Root>(原始JSON字符串, jsonOptions);
方案2:手动映射(轻量场景适用)
如果仅需要反序列化、不需要双向支持,不需要写自定义转换器,可利用原生Dictionary对动态键的直接映射能力,手动完成结构转换:
// 定义临时匹配结构 internal class TempRoot { public Dictionary<int, List<Warning>> Warnings { get; set; } } // 反序列化临时结构 var tempResult = JsonSerializer.Deserialize<TempRoot>(原始JSON字符串, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); // 转换为目标类结构 var root = new Root { WarningAreas = tempResult.Warnings.Select(kv => new WarningArea { AreaId = kv.Key, Warnings = kv.Value }).ToList() };
注意事项
- 自定义转换器的实现全程基于System.Text.Json原生高性能API,无第三方依赖,性能高于Newtonsoft.Json的同逻辑实现
- 自定义转换器中加入了类型转换容错逻辑,遇到非数字格式的动态键会自动跳过,不会导致整个反序列化流程异常
- 两种方案都不需要修改预先定义的
Root、WarningArea、Warning类的代码,无业务侵入
内容的提问来源于stack exchange,提问作者jwillmer
相关产品推荐
相关产品推荐

