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

Mik*_*ike 11 c# json .net-core system.text.json

我收到来自 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);
}
Run Code Online (Sandbox Code Playgroud)

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

我在 Github 的 corefx repo 中找到了这个移植指南,其中部分Read from a Stream/String指出:

我们目前(从 .NET Core 3.0 预览版 2 开始)没有方便的 API 来直接(同步或异步)从流中读取 JSON。对于同步读取(尤其是小型有效负载),您可以将 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;
}
Run Code Online (Sandbox Code Playgroud)

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

这个实现可能在很多方面都可以改进,但最让我烦恼的是从池中租用字节数组,例如Stream.Lengthlong 并且我将它转换为 int ,这可能导致OverflowException.

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

Mik*_*ray 26

我认为文档需要更新,因为 .NET Core 3 有一种直接从流中读取的方法。使用它很简单,假设流是用 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;
}
Run Code Online (Sandbox Code Playgroud)

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

var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
Run Code Online (Sandbox Code Playgroud)

  • @DirkBoer 不确定您使用的是哪个 .NET Core 版本,但在我们的例子中,我们使用的是 .NET Core 3.1 并且流中“DeserializeAsync”的实现负责[将流切碎到缓冲区并从中读取] (https://github.com/dotnet/runtime/blob/master/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs#L89)。 (3认同)