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

如何用JsonSerializer.DeserializeAsyncEnumerable流式处理非根级测量数组

流式读取被Header包裹的大型JSON数组(System.Text.Json)

需要用JsonSerializer.DeserializeAsyncEnumerable()流式处理服务器返回的大型JSON响应,但测量数据数组被包裹在根级的Header对象中,结构定义如下:

public record Header(string Id, Measurement[] Measurements);
public record Measurement(string Timestamp, decimal Value);

JsonSerializer.DeserializeAsyncEnumerable()仅支持根级为数组的场景,且无需Header内容,目标是避免加载整个数据集到内存(比如计算测量值平均值)。之前尝试自定义JsonConverter和将Header的Measurements属性改为IAsyncEnumerable<Measurement>均未解决问题。


解决方案

可以通过Utf8JsonReader先定位到Measurements数组的起始位置,再逐个流式反序列化数组中的元素,完全避免加载整个数组到内存。具体步骤如下:

1. 异步获取响应流

使用HttpCompletionOption.ResponseHeadersRead确保只读取响应头,响应体留存在流中,不提前加载到内存:

var httpClient = new HttpClient();
using var response = await httpClient.GetAsync("目标API地址", HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync();

2. 用Utf8JsonReader定位到Measurements数组

遍历根Header对象的属性,跳过不需要的内容,直到找到"Measurements"对应的数组起始令牌:

var reader = new Utf8JsonReader(stream, new JsonReaderOptions { AllowTrailingCommas = true });

// 跳过根对象的StartObject令牌
await reader.ReadAsync();
if (reader.TokenType != JsonTokenType.StartObject)
    throw new InvalidOperationException("响应根节点不是预期的对象类型");

// 遍历根对象属性,寻找Measurements数组
while (await reader.ReadAsync())
{
    if (reader.TokenType == JsonTokenType.PropertyName && reader.GetString() == "Measurements")
    {
        // 读取Measurements属性的值令牌(应为StartArray)
        await reader.ReadAsync();
        if (reader.TokenType != JsonTokenType.StartArray)
            throw new InvalidOperationException("Measurements字段不是预期的数组类型");
        
        // 定位完成,开始处理数组元素
        break;
    }
    // 跳过当前属性的整个值(避免加载到内存)
    await reader.SkipAsync();
}

3. 流式处理每个Measurement对象

从数组起始位置开始,逐个反序列化Measurement对象并实时处理(比如计算平均值):

decimal totalValue = 0;
int measurementCount = 0;

while (await reader.ReadAsync())
{
    // 到达数组末尾,停止处理
    if (reader.TokenType == JsonTokenType.EndArray)
        break;
    
    // 反序列化单个Measurement对象
    if (reader.TokenType == JsonTokenType.StartObject)
    {
        var measurement = await JsonSerializer.DeserializeAsync<Measurement>(ref reader);
        totalValue += measurement.Value;
        measurementCount++;
    }
}

// 计算并输出平均值
if (measurementCount > 0)
{
    var average = totalValue / measurementCount;
    Console.WriteLine($"测量值平均值:{average}");
}

为什么之前的尝试失败?

  • 自定义JsonConverter:如果没有针对流式场景设计,常规Converter会尝试加载整个数组到内存后再处理,无法实现真正的流式;
  • IAsyncEnumerable属性:System.Text.Json默认的集合反序列化逻辑会先完整读取整个数组,再生成异步枚举器,因此即使不迭代,所有Measurement对象已被加载到内存。

内容的提问来源于stack exchange,提问作者Erwin van der Valk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 17:40:23