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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 08:15:05