Reading JSON into Models
GetFromJsonAsync and ReadFromJsonAsync turn an API's JSON into your own model objects, matching names whatever their capitals. PostAsJsonAsync and PutAsJsonAsync send objects as JSON. Check the answer's status, and read the saved item from a 201 or a PUT's 200.
Journeyman · C# · REST API · Updated
After this lesson you can
- Read JSON into model objects with GetFromJsonAsync and ReadFromJsonAsync
- Send objects as JSON with PostAsJsonAsync and PutAsJsonAsync, and check the answer
Key takeaways
GetFromJsonAsync<Category>sends aGETand turns the JSON into aCategory; a JSON array becomes aList<Category>.- JSON names match your model's properties whatever their capitals, so
categoryNamefillsCategoryName. GetFromJsonAsyncthrows on a404. When a missing item is a normal answer, useGetAsync, check the status, thenReadFromJsonAsync<T>.PostAsJsonAsyncandPutAsJsonAsyncsend an object as JSON. A201carries the new item to read, and aPUT's200the updated one; a204has no body.JsonSerializerwithJsonSerializerOptions.Webdoes the same conversions on strings, such as JSON from a file.
The API sends JSON; your program wants objects. Turning JSON into objects is called deserializing, and turning objects into JSON is serializing. The System.Net.Http.Json methods do both as part of the request, so you never handle the JSON text yourself.
A model for the data
The client needs its own class shaped like the JSON. It's usually a copy of the API's model:
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; } = "";
public string? Description { get; set; }
}
The JSON says categoryName; the class says CategoryName. That's fine: these methods match names whatever their capitals, the same way ASP.NET Core does on the other side.
Reading: GetFromJsonAsync
GetFromJsonAsync<T> sends a GET, checks for success, and deserializes the body into a T, in one call:
using System.Net.Http.Json;
using HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:5179/");
Category? seafood = await client.GetFromJsonAsync<Category>("api/categories/8");
Console.WriteLine( quot;{seafood?.CategoryName}: {seafood?.Description}");
List<Category>? categories = await client.GetFromJsonAsync<List<Category>>("api/categories");
Console.WriteLine( quot;{categories?.Count} categories, starting with {categories?[0].CategoryName}");
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; } = "";
public string? Description { get; set; }
}
Seafood: Seaweed and fish
8 categories, starting with Beverages
A JSON object becomes a Category; a JSON array becomes a List<Category>. The results are nullable, because the body could be null.
GetFromJsonAsync throws an HttpRequestException when the status isn't a success, such as a 404. When a missing item is a normal answer, use GetAsync and check the status first, then read the body with ReadFromJsonAsync<T>:
using System.Net;
using System.Net.Http.Json;
using HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:5179/");
foreach (int id in new[] { 7, 300 })
{
HttpResponseMessage response = await client.GetAsync( quot;api/categories/{id}");
if (response.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine( quot;{id}: no such category");
continue;
}
response.EnsureSuccessStatusCode();
Category? category = await response.Content.ReadFromJsonAsync<Category>();
Console.WriteLine( quot;{id}: {category?.CategoryName}");
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; } = "";
public string? Description { get; set; }
}
7: Produce
300: no such category
Sending: PostAsJsonAsync and PutAsJsonAsync
PostAsJsonAsync serializes an object into the request body, sets Content-Type: application/json, and sends a POST. PutAsJsonAsync does the same with a PUT, and DeleteAsync sends a DELETE. Each returns the response, for you to check:
using System.Net.Http.Json;
using HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:5179/");
Category snacks = new Category { CategoryName = "Snacks", Description = "Chips, nuts and pretzels" };
HttpResponseMessage created = await client.PostAsJsonAsync("api/categories", snacks);
Category? added = await created.Content.ReadFromJsonAsync<Category>();
Console.WriteLine( quot;POST: {(int)created.StatusCode}, new id given: {added?.CategoryId > 8}");
Console.WriteLine( quot;It lives at {created.Headers.Location?.AbsolutePath == quot;/api/categories/{added?.CategoryId}"}");
added!.Description = "Chips, nuts, pretzels and popcorn";
HttpResponseMessage updated = await client.PutAsJsonAsync( quot;api/categories/{added.CategoryId}", added);
Category? saved = await updated.Content.ReadFromJsonAsync<Category>();
Console.WriteLine( quot;PUT: {(int)updated.StatusCode}, now: {saved?.Description}");
HttpResponseMessage deleted = await client.DeleteAsync( quot;api/categories/{added.CategoryId}");
Console.WriteLine( quot;DELETE: {(int)deleted.StatusCode}");
HttpResponseMessage invalid = await client.PostAsJsonAsync("api/categories", new Category { Description = "No name" });
Console.WriteLine( quot;POST with no name: {(int)invalid.StatusCode}");
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; } = "";
public string? Description { get; set; }
}
POST: 201, new id given: True
It lives at True
PUT: 200, now: Chips, nuts, pretzels and popcorn
DELETE: 204
POST with no name: 400
- The
201response's body is the new category, with the id the database gave it: read it withReadFromJsonAsync. created.Headers.Locationholds the new category's address.- A
PUTanswers200, and its body is the category as it's now saved: read it withReadFromJsonAsync, as for the201. - A
DELETEanswers204, with no body to read. - The
POSTwith no name never reached the database: the API answered400, and the program found out from the status code.
Without HttpClient: JsonSerializer
When JSON comes from somewhere else, such as a file, JsonSerializer (in System.Text.Json) does the same conversions on strings. Pass JsonSerializerOptions.Web to get the web's rules: names matched whatever their capitals, and camelCase when writing.
using System.Text.Json;
string json = """{"categoryId":8,"categoryName":"Seafood","description":"Seaweed and fish"}""";
Category? category = JsonSerializer.Deserialize<Category>(json, JsonSerializerOptions.Web);
Console.WriteLine(category?.CategoryName);
category!.Description = "Fish and shellfish";
Console.WriteLine(JsonSerializer.Serialize(category, JsonSerializerOptions.Web));
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; } = "";
public string? Description { get; set; }
}
Seafood
{"categoryId":8,"categoryName":"Seafood","description":"Fish and shellfish"}
Check yourself
2 questions to try on the lesson page.
So what
With a model class and the ...AsJsonAsync methods, an API feels like a set of methods that take and return your own objects. Check each response's status, read what comes back, and the JSON looks after itself.