Home > Software engineering >  How to deserialize HttpClient results from array of arrays to a list of an object
How to deserialize HttpClient results from array of arrays to a list of an object

Time:06-04

I am calling an API like so:

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;
}

This returns a bunch of results like so:

[
    [
        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
    ]
]

I was wondering if HttpClient in .NET5 offers simple way to convert these results into a List<MyResponseObject>?

CodePudding user response:

Suppose you have an API exposing a GET endpoint at /api/students.

This endpoint returns the following JSON response:

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

In order to call this endpoint you can define the following POCO class, to deserialize every single array item:

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

The simplest approach to call the endpoint and deserialize the HTTP response to a .NET object is the following:

  • issue the GET request and wait for the request to complete
  • read the response content as a string, containing the JSON returned by the endpoint
  • deserialize the JSON into a .NET object by using a library of your choice

In the following example I use the built-in JSON support of .NET core in order to deserialize the JSON string into a .NET object (another common choice is using the 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);

Some important notes on the previous code:

  1. do not manually create and dispose instances of HttpClient. Use, insted, the IHttpClientFactory to do that. You can read more about this topic here and here. In the second article you'll also find an explanation on why you should never manually create and dispose instances of HttpClient.

  2. when you do I/O operations (such as issuing HTTP requests) and the class you are using have native support for async code, never ever block on asynchronous method calls. Instead of using blocking method such as Task.Result or Task.Wait(), make your own code asynchronous (by using the async keyword) and perform asynchronous wait on tasks (by using the await keyword). You can learn more about asynchronous programming in .NET with async and await here. This article explains why you should never block when calling asynchronous methods.

The approach showed above to deserialize the JSON returned by a web API is not very efficient. That's the simplest possible code and it's ok if the JSON response content is not too big and you are not working in a performance critical application.

The main issue with this code is that you are buffering in memory the entire response content as a string before deserializing it to a .NET object.

By doign so you are wasting memory, allocating unnecessary objects and delaying the moment when you actually start working on the response content (you can start working on the response content only when the entire response has been received from the network and its entire content has been buffered in memory as a string).

This article explains this problem in depth.

A better approach is the following:

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);

I'm currently writing a very simple library which helps working with JSON apis from .NET core applications. It already encapsulates these concepts and add some more exception handling and corner case handling. You can find the source code here.

As a sidenote, you mentioned that you are expecting to receive and array of array inside the response content. My suggestion in this case is using the code showed above to deserialize the response inside a List<List<double>>. After that, you can decide to flatten the sequence by using the SelectMany extension method:

var rawResponseContent = await JsonSerializer.DeserializeAsync<List<List<double>>>(stream, serializerOptions);

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