如何使用 System.Text.Json API 将流反序列化为对象

Posted

技术标签:

【中文标题】如何使用 System.Text.Json API 将流反序列化为对象【英文标题】:How to deserialize stream to object using System.Text.Json APIs 【发布时间】:2020-02-19 02:00:12 【问题描述】:

我正在以流形式接收来自 Web api 调用的响应,需要将其反序列化为模型。

这是一个通用方法,所以我不能说代码的哪些部分会使用它以及响应负载是什么。

方法如下:

public async Task<T> InvokeAsync<T>(string method)

    Stream response = await this.httpClientWrapper.InvokeAsync(method);
    var serializer = new JsonSerializer();
    using var streamReader = new StreamReader(response);
    using var reader = new JsonTextReader(streamReader);
    return serializer.Deserialize<T>(reader);

我正在尝试删除 Newtonsoft 并使用 System.Text.Json API。

我在 Github 的 corefx 存储库中找到了这个 porting guide,其中 Reading from a Stream/String 部分指出:

我们目前(截至 .NET Core 3.0 预览版 2)没有方便的 直接从流中读取 JSON 的 API(同步或 异步)。对于同步阅读(尤其是小 有效负载),您可以读取 JSON 有效负载直到流结束 放入一个字节数组并将其传递给阅读器

因此,按照这个建议,我提出了以下建议:

public async Task<T> InvokeAsync<T>(string method)

    Stream response = await this.httpClientWrapper.InvokeAsync(method);
    var length = response.Length;
    var buffer = ArrayPool<byte>.Shared.Rent((int)length);
    var memory = new Memory<byte>(buffer);
    await response.WriteAsync(memory);
    var result = JsonSerializer.Deserialize<T>(memory.Span);
    ArrayPool<byte>.Shared.Return(buffer);
    return result;

所以我的问题是 - 我是否正确理解了建议并且这是要走的路?

这个实现可能在很多方面都可以改进,但最困扰我的是从池中租用字节数组,例如Stream.Length 很长,我将其转换为 int 可能会导致 OverflowException

我试图研究 System.IO.Pipelines 并使用 JSON API 的 ReadOnlySequence&lt;byte&gt; 重载,但它变得非常复杂。

【问题讨论】:

【参考方案1】:

我认为文档需要更新,因为 .NET Core 3 有一个 method 可以直接从流中读取。使用它很简单,假设流是用 UTF8 编码的:

private static readonly JsonSerializerOptions Options = new JsonSerializerOptions();

private static async Task<T> Deserialize<T>(HttpResponseMessage response)

    var contentStream = await response.Content.ReadAsStreamAsync();
    var result = await JsonSerializer.DeserializeAsync<T>(contentStream, Options);
    return result;

需要注意的一点是,默认情况下,HttpClient 会在返回之前将响应内容缓存在内存中,除非您在调用 SendAsync 时将 HttpCompletionOption 设置为 ResponseHeadersRead:

var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);

【讨论】:

感谢您的回复。后来我看到了这个 API,但现在我在质疑更大的有效载荷。分割整个流并读取缓冲区由JsonTextReader 完成。 System.Text.Json API 似乎不是这种情况,但这是另一个问题。 嗨@Mike,你找到更大有效载荷的解决方案了吗? @DirkBoer 不确定您使用的是哪个 .NET Core 版本,但在我们的例子中,我们使用的是 .NET Core 3.1 并且来自流的DeserializeAsync 的实现负责chopping up the stream to buffers and reading from them。

以上是关于如何使用 System.Text.Json API 将流反序列化为对象的主要内容,如果未能解决你的问题,请参考以下文章

将 false 反序列化为 null (System.Text.Json)

System.Text.Json API 有类似 IContractResolver 的东西

System.Text.Json 反序列化来自 API 调用的嵌套对象 - 数据包装在父 JSON 属性中

如何使用 System.Text.Json 忽略错误值

如何在反序列化之前使用 System.Text.Json 验证 JSON

使用System.Text.Json(.netcore-3.0)代替Newtonsoft.Json的扩展功能是什么?