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

How to deserialize stream to object using System.Text.Json APIs

我正在接收来自网络 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。

我发现这个 porting guide in corefx repo in Github, where section Reading from a Stream/String 状态:

We currently (as of .NET Core 3.0 preview 2) do not have a convenient API to read JSON from a stream directly (either synchronously or asynchronously). For synchronous reading (especially of small payloads), you could read the JSON payload till the end of the stream into a byte array and pass that into the reader

所以根据这个建议,我提出了以下建议:

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 是一个 long,我将它转换为 int,这会导致 OverflowException.

我尝试研究 System.IO.Pipelines 并使用 JSON API 的 ReadOnlySequence<byte> 重载,但它变得非常复杂。

我认为文档需要更新,因为 .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);