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



我收到一个来自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 repo中找到了这个移植指南,其中Reading 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;
}

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

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

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

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

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

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

从ASP.NET Core 6开始,您应该使用DeserializeAsyncEnumerable((方法:

using var request = new HttpRequestMessage(HttpMethod.Get, "/api/products");
request.SetBrowserResponseStreamingEnabled(true); // Enable response streaming
using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
IAsyncEnumerable<Product?> products = JsonSerializer.DeserializeAsyncEnumerable<Product>(stream, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
DefaultBufferSize = 128
});
await foreach (Product? product in products)
{
// ...
}
}

最新更新