Table of contents

How do I deserialize JSON responses with HttpClient (C#)?

Deserializing JSON responses from HttpClient is a common task in C# web development. This guide covers the two main approaches: System.Text.Json (recommended for .NET 5+) and Newtonsoft.Json for legacy compatibility.

Quick Example

Here's a minimal example using System.Text.Json:

using System.Net.Http;
using System.Text.Json;

var httpClient = new HttpClient();
var response = await httpClient.GetAsync("https://api.example.com/users/1");
var json = await response.Content.ReadAsStringAsync();
var user = JsonSerializer.Deserialize<User>(json);

Method 1: Using System.Text.Json (Recommended)

System.Text.Json is built into .NET 5+ and offers better performance than Newtonsoft.Json.

Required Namespaces

using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

Complete Example

public class User
{
    [JsonPropertyName("id")]
    public int Id { get; set; }

    [JsonPropertyName("name")]
    public string Name { get; set; }

    [JsonPropertyName("email")]
    public string Email { get; set; }

    [JsonPropertyName("created_at")]
    public DateTime CreatedAt { get; set; }
}

public class ApiService
{
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _jsonOptions;

    public ApiService()
    {
        _httpClient = new HttpClient();
        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
        };
    }

    public async Task<User> GetUserAsync(int userId)
    {
        try
        {
            var response = await _httpClient.GetAsync($"https://api.example.com/users/{userId}");
            response.EnsureSuccessStatusCode();

            var json = await response.Content.ReadAsStringAsync();
            var user = JsonSerializer.Deserialize<User>(json, _jsonOptions);

            return user;
        }
        catch (HttpRequestException ex)
        {
            throw new Exception($"HTTP request failed: {ex.Message}", ex);
        }
        catch (JsonException ex)
        {
            throw new Exception($"JSON deserialization failed: {ex.Message}", ex);
        }
    }

    public async Task<List<User>> GetUsersAsync()
    {
        var response = await _httpClient.GetAsync("https://api.example.com/users");
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        var users = JsonSerializer.Deserialize<List<User>>(json, _jsonOptions);

        return users ?? new List<User>();
    }
}

Extension Method Approach

Create an extension method for cleaner code:

public static class HttpClientExtensions
{
    private static readonly JsonSerializerOptions DefaultOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    };

    public static async Task<T> GetFromJsonAsync<T>(this HttpClient httpClient, string requestUri)
    {
        var response = await httpClient.GetAsync(requestUri);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<T>(json, DefaultOptions);
    }
}

// Usage
var user = await httpClient.GetFromJsonAsync<User>("https://api.example.com/users/1");

Method 2: Using Newtonsoft.Json

For legacy projects or when you need advanced JSON features not available in System.Text.Json.

Installation

dotnet add package Newtonsoft.Json

Example Implementation

using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;

public class User
{
    [JsonProperty("id")]
    public int Id { get; set; }

    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("email")]
    public string Email { get; set; }

    [JsonProperty("created_at")]
    public DateTime CreatedAt { get; set; }
}

public class ApiService
{
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerSettings _jsonSettings;

    public ApiService()
    {
        _httpClient = new HttpClient();
        _jsonSettings = new JsonSerializerSettings
        {
            DateFormatHandling = DateFormatHandling.IsoDateFormat,
            NullValueHandling = NullValueHandling.Ignore,
            MissingMemberHandling = MissingMemberHandling.Ignore
        };
    }

    public async Task<User> GetUserAsync(int userId)
    {
        try
        {
            var response = await _httpClient.GetAsync($"https://api.example.com/users/{userId}");
            response.EnsureSuccessStatusCode();

            var json = await response.Content.ReadAsStringAsync();
            var user = JsonConvert.DeserializeObject<User>(json, _jsonSettings);

            return user;
        }
        catch (HttpRequestException ex)
        {
            throw new Exception($"HTTP request failed: {ex.Message}", ex);
        }
        catch (JsonException ex)
        {
            throw new Exception($"JSON deserialization failed: {ex.Message}", ex);
        }
    }
}

Advanced Scenarios

Handling Nested Objects

public class ApiResponse<T>
{
    [JsonPropertyName("data")]
    public T Data { get; set; }

    [JsonPropertyName("success")]
    public bool Success { get; set; }

    [JsonPropertyName("message")]
    public string Message { get; set; }
}

// Usage
var response = await httpClient.GetFromJsonAsync<ApiResponse<User>>("https://api.example.com/users/1");
var user = response.Data;

Custom Converters

public class DateTimeConverter : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return DateTime.ParseExact(reader.GetString(), "yyyy-MM-dd HH:mm:ss", null);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
    }
}

// Configure options
var options = new JsonSerializerOptions();
options.Converters.Add(new DateTimeConverter());

Best Practices

1. Proper HttpClient Usage

// Use dependency injection or singleton pattern
public class HttpClientService
{
    private static readonly HttpClient _httpClient = new HttpClient();

    // Or use IHttpClientFactory in ASP.NET Core
    public HttpClientService(IHttpClientFactory httpClientFactory)
    {
        _httpClient = httpClientFactory.CreateClient();
    }
}

2. Error Handling

public async Task<T> SafeDeserializeAsync<T>(HttpResponseMessage response)
{
    try
    {
        response.EnsureSuccessStatusCode();
        var json = await response.Content.ReadAsStringAsync();

        if (string.IsNullOrEmpty(json))
            return default(T);

        return JsonSerializer.Deserialize<T>(json);
    }
    catch (HttpRequestException ex)
    {
        // Log and handle HTTP errors
        throw new ApiException($"Request failed: {ex.Message}");
    }
    catch (JsonException ex)
    {
        // Log and handle JSON parsing errors
        throw new ApiException($"Invalid JSON response: {ex.Message}");
    }
}

3. Configuration

public static class JsonConfig
{
    public static readonly JsonSerializerOptions DefaultOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        NumberHandling = JsonNumberHandling.AllowReadingFromString
    };
}

Performance Considerations

  • System.Text.Json is faster and uses less memory than Newtonsoft.Json
  • Reuse JsonSerializerOptions instances to avoid repeated configuration
  • Consider using ReadOnlySpan<byte> overloads for better performance with large responses
  • Use streaming APIs for very large JSON responses

Common Issues

Property Name Mismatch

Use [JsonPropertyName] attribute or configure naming policies:

public class User
{
    [JsonPropertyName("user_id")]
    public int Id { get; set; }
}

Date Format Issues

Configure date handling explicitly:

var options = new JsonSerializerOptions
{
    Converters = { new DateTimeConverter() }
};

This comprehensive approach ensures robust JSON deserialization with proper error handling and performance optimization.

Try WebScraping.AI for Your Web Scraping Needs

Looking for a powerful web scraping solution? WebScraping.AI provides an LLM-powered API that combines Chromium JavaScript rendering with rotating proxies for reliable data extraction.

Key Features:

  • AI-powered extraction: Ask questions about web pages or extract structured data fields
  • JavaScript rendering: Full Chromium browser support for dynamic content
  • Rotating proxies: Datacenter and residential proxies from multiple countries
  • Easy integration: Simple REST API with SDKs for Python, Ruby, PHP, and more
  • Reliable & scalable: Built for developers who need consistent results

Getting Started:

Get page content with AI analysis:

curl "https://api.webscraping.ai/ai/question?url=https://example.com&question=What is the main topic?&api_key=YOUR_API_KEY"

Extract structured data:

curl "https://api.webscraping.ai/ai/fields?url=https://example.com&fields[title]=Page title&fields[price]=Product price&api_key=YOUR_API_KEY"

Try in request builder

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon