如何将 HttpClient 结果从数组数组反序列化为对象列表

How to deserialize HttpClient results from array of arrays to a list of an object

我这样调用 API:

using (HttpClient client = new HttpClient())
{
    client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(".NET", "5.0"));
    var result = client.GetAsync("https://the.api.com").Result;
    var data = result.Content.ReadAsStringAsync().Result;
}

这returns一堆这样的结果:

[
    [
        1647993600,
        2963,
        2972.18,
        2970.51,
        2967.14,
        831.59875656
    ],
    [
        1647993300,
        2967.91,
        2972.14,
        2970.95,
        2970.51,
        588.06561288
    ],
    [
        1647993000,
        2969.91,
        2977.03,
        2976.83,
        2970.84,
        444.49625293
    ]
]

我想知道 .NET5 中的 HttpClient 是否提供了将这些结果转换为 List<MyResponseObject> 的简单方法?

假设您有一个 API 在 /api/students 公开一个 GET 端点。

此端点 returns 以下 JSON 响应:

[
    {
      "name": "Bob",
      "age": 20
    },
    {
      "name": "Alice",
      "age": 34
    }
]

为了调用此端点,您可以定义以下 POCO class,以反序列化每个数组项:

public sealed class Student 
{
  public string Name { get; set; }
  public int Age { get; set; }
}

调用端点并将 HTTP 响应反序列化为 .NET 对象的最简单方法如下:

  • 发出 GET 请求并等待请求完成
  • 读取响应内容为字符串,包含端点返回的JSON
  • 使用您选择的库将 JSON 反序列化为 .NET 对象

在下面的示例中,我使用了 .NET Core 的 built-in JSON 支持,以便将 JSON 字符串反序列化为 .NET 对象(另一个常见的选择是使用Newtonsft JSON library). The .NET core built-in support for JSON serialization and deserialization is inside the System.Text.Json namespace. The documentation is here.

var uri = new Uri("https://my-service.example.com/api/students");
var response = await httpClient.GetAsync(uri).ConfigureAwait(false);

response.EnsureSuccessStatusCode();

var json = response.Content.ReadAsStringAsync().ConfigureAwait(false);

var serializerOptions = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true
};
var students = JsonSerializer.Deserialize<List<Student>>(json, serializerOptions);

关于前面代码的一些重要说明:

  1. 不要手动创建和处置 HttpClient 的实例。相反,使用 IHttpClientFactory 来做到这一点。您可以阅读有关此主题的更多信息 here and here。在第二篇文章中,您还将找到关于为什么 永远不应手动创建和处置 HttpClient.

    实例的解释
  2. 当您执行 I/O 操作(例如发出 HTTP 请求)并且您使用的 class 具有对异步代码的本机支持时,永远不会阻止异步方法调用。不要使用 Task.ResultTask.Wait() 等阻塞方法,而是让您自己的代码异步(通过使用 async 关键字)并在任务上执行异步等待(通过使用 await关键词)。您可以通过 asyncawait 了解有关 .NET 中异步编程的更多信息 here. This article 解释了为什么 在调用异步方法时您不应该阻塞 .

上面显示的反序列化网络 API 返回的 JSON 的方法不是很有效。这是最简单的代码,如果 JSON 响应内容不是太大并且您不是在性能关键型应用程序中工作就可以了。

此代码的主要问题是,在将其反序列化为 .NET 对象之前,您将整个响应内容作为字符串缓冲在内存中。

这样做是在浪费内存,分配不必要的对象并延迟实际开始处理响应内容的时间(只有当 整个响应 已从网络接收到,其全部内容已作为字符串缓冲在内存中)。

This article explains this problem in depth.

更好的方法如下:

var uri = new Uri("https://my-service.example.com/api/students");

/*
* The task returned by GetAsync completes as soon as the HTTP response header have been read.
* Remember to Dispose the HttpResponseMessage object.
*/
using var response = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);

response.EnsureSuccessStatusCode();

/*
* Read the response content directly from the network socket. 
* By doing so data is processed as it is being received from the network: this is more efficient because we avoid buffering and we start processing
* data as soon as they arrive.
*/
var stream = await response.Content.ReadAsStreamAsync();

var serializerOptions = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true
}; 
var students = await JsonSerializer.DeserializeAsync<List<Student>>(stream, serializerOptions);

我目前正在编写一个非常简单的库,它有助于使用来自 .NET 核心应用程序的 JSON api。它已经封装了这些概念并添加了更多的异常处理和极端情况处理。你可以找到源代码here.

作为旁注,您提到您希望在响应内容中接收到数组和数组。在这种情况下,我的建议是使用上面显示的代码反序列化 List<List<double>> 内的响应。之后,您可以决定使用 SelectMany extension method:

来展平序列
var rawResponseContent = await JsonSerializer.DeserializeAsync<List<List<double>>>(stream, serializerOptions);

List<double> numbers = rawResponseContent.SelectMany(x => x).ToList();