WPF中使用System.Net.Json反序列化含动态键值的JSON
解决System.Text.Json反序列化动态键字典到强类型数组的问题
问题场景
需要在WPF应用中用System.Text.Json反序列化一段JSON,其中intervals数组的每个对象包含固定的timestamp字段,以及数量、名称均不固定的数字字符串键的子对象,每个子对象内是subintervals数组。
示例JSON:
{ "intervals": [ { "timestamp": 1677477728, "123456": { "subintervals": [ { "max": "56.7", "label": "Leq" }, { "max": "58.1", "label": "Lmax" } ] } }, { "timestamp": 1677477730, "54321": { "subintervals": [ { "value": "58.5", "label": "Leq" }, { "value": "59.5", "label": "Lmax" } ] }, "56789": { "subintervals": [ { "value": "78.2", "label": "Lmax" }, { "value": "74.3", "label": "Leq" } ] } } ] }
当前已实现用JsonExtensionData获取JsonElement字典,但希望直接映射为Dictionary<string, SubInterval[]>,其中SubInterval定义:
public class SubInterval { public string? Max { get; set; } public string? Label { get; set; } // 兼容JSON里的value字段,避免数据丢失 public string? Value { get; set; } }
遇到的问题:
- 自定义JsonConverter无法生效
JsonExtensionData仅支持Dictionary<string, JsonElement>或Dictionary<string, object>,无法直接用于强类型字典- 转换器内部不知道如何正确读取动态键、反序列化子节点、循环处理内容
解决方案
1. 修正自定义转换器的泛型类型与读取逻辑
首先修正转换器的泛型参数,使其匹配目标类型Dictionary<string, SubInterval[]>,然后正确实现Read方法的JSON读取逻辑:
public class MeasurementPointDictionaryConverter : JsonConverter<Dictionary<string, SubInterval[]>> { public override Dictionary<string, SubInterval[]>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // 校验当前读取位置是否为对象起始 if (reader.TokenType != JsonTokenType.StartObject) { throw new JsonException("Expected start of object."); } var dict = new Dictionary<string, SubInterval[]>(); while (reader.Read()) { // 遇到对象结束标记,退出循环 if (reader.TokenType == JsonTokenType.EndObject) { break; } // 读取动态键(数字字符串) if (reader.TokenType != JsonTokenType.PropertyName) { throw new JsonException("Expected property name."); } var pointId = reader.GetString() ?? string.Empty; // 读取键对应的子对象 reader.Read(); if (reader.TokenType != JsonTokenType.StartObject) { throw new JsonException("Expected start of measurement point object."); } SubInterval[]? subIntervals = null; // 遍历子对象内的属性,定位到subintervals数组 while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) { break; } if (reader.TokenType == JsonTokenType.PropertyName && reader.GetString() == "subintervals") { // 反序列化subintervals数组为强类型数组 reader.Read(); subIntervals = JsonSerializer.Deserialize<SubInterval[]>(ref reader, options); } else { // 跳过未知属性,避免读取异常 reader.Skip(); } } dict.Add(pointId, subIntervals ?? Array.Empty<SubInterval>()); } return dict; } public override void Write(Utf8JsonWriter writer, Dictionary<string, SubInterval[]> value, JsonSerializerOptions options) { throw new NotImplementedException(); } }
2. 配置IntervalMeta类
移除JsonExtensionData特性,直接在MeasurementPoints属性上添加自定义转换器:
public class IntervalMeta { [JsonPropertyName("timestamp")] public long? Timestamp { get; set; } [JsonConverter(typeof(MeasurementPointDictionaryConverter))] public Dictionary<string, SubInterval[]>? MeasurementPoints { get; set; } = new(); } // 根对象类,用于包裹intervals数组 public class RootObject { [JsonPropertyName("intervals")] public List<IntervalMeta>? Intervals { get; set; } }
3. 执行反序列化
string json = "你的JSON字符串"; var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true // 忽略大小写,兼容JSON字段名 }; var result = JsonSerializer.Deserialize<RootObject>(json, options);
关键说明
- 移除
JsonExtensionData:该特性仅用于捕获未映射的JSON属性到特定类型字典,无法直接转换为强类型字典,因此改用自定义转换器直接处理属性的反序列化。 - 转换器逻辑:通过
Utf8JsonReader逐token读取,先捕获动态键,再深入到对象内找到subintervals数组进行反序列化,跳过其他未知属性。 - 兼容
value字段:在SubInterval类中添加Value属性,确保JSON里的value字段能正确映射,避免数据丢失。
内容的提问来源于stack exchange,提问作者Erik Thysell
相关产品推荐
相关产品推荐

