JSON Serialization
JSON is the lingua franca of data exchange in modern applications. .NET 6+ ships System.Text.Json as the built-in, high-performance JSON library. No extra packages are needed for the common 80% of scenarios.
JsonSerializer.Serialize / Deserialize
The two key static methods:
string json = JsonSerializer.Serialize(myObject);
MyType obj = JsonSerializer.Deserialize<MyType>(json)!;Serialize converts any .NET object to a JSON string. Deserialize<T> parses a JSON string and populates a new T instance.
JsonSerializerOptions
Customise serialisation behaviour through JsonSerializerOptions:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};PropertyNamingPolicy.CamelCase—"firstName"instead of"FirstName"WriteIndented— pretty-print for human readabilityDefaultIgnoreCondition— skip null/default properties
Attributes
Control serialisation on individual properties:
[JsonPropertyName("first_name")]— use a custom JSON key[JsonIgnore]— exclude the property entirely[JsonPropertyOrder(1)]— control output order
Async JSON (ReadFromJsonAsync)
For HTTP responses and streams, use the async helpers in System.Net.Http.Json:
var user = await httpClient.GetFromJsonAsync<User>("https://api.example.com/user/1");Or with streams:
var data = await JsonSerializer.DeserializeAsync<T>(stream, options);Newtonsoft.Json (Json.NET)
Before System.Text.Json, Newtonsoft.Json (NuGet: Newtonsoft.Json) was the de-facto standard. It is still widely used for its richer feature set: polymorphic serialisation, LINQ to JSON (JObject/JArray), and more permissive parsing. Install it when you need those features or are working with a legacy codebase. The API is similar: JsonConvert.SerializeObject(obj) / JsonConvert.DeserializeObject<T>(json).
Performance Tip
Reuse JsonSerializerOptions instances — creating one is relatively expensive. Store them as a static readonly field.
Code Examples
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Product
{
[JsonPropertyName("product_id")]
public int Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; } = "";
public decimal Price { get; set; }
public List<string> Tags { get; set; } = new();
[JsonIgnore]
public string InternalCode { get; set; } = ""; // excluded from JSON
}
var product = new Product
{
Id = 101,
Name = "Wireless Headphones",
Price = 79.99m,
Tags = new List<string> { "audio", "wireless", "bluetooth" },
InternalCode = "WH-2024-SECRET",
};
// Default — compact, uses attribute names
string compactJson = JsonSerializer.Serialize(product);
Console.WriteLine("Compact:");
Console.WriteLine(compactJson);
// Indented
var opts = new JsonSerializerOptions { WriteIndented = true };
string prettyJson = JsonSerializer.Serialize(product, opts);
Console.WriteLine("\nPretty:");
Console.WriteLine(prettyJson);[JsonPropertyName] controls the JSON key name. [JsonIgnore] completely excludes a property. WriteIndented:true produces human-readable output. Note that InternalCode is absent from both outputs.
using System;
using System.Collections.Generic;
using System.Text.Json;
public record Address(string Street, string City, string Country);
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Email { get; set; } = "";
public Address? Address { get; set; }
public List<string> Roles { get; set; } = new();
}
string json = """
{
"Id": 42,
"Name": "Jane Doe",
"Email": "jane@example.com",
"Address": {
"Street": "123 Main St",
"City": "Springfield",
"Country": "US"
},
"Roles": ["admin", "editor"]
}
""";
// Deserialise
var customer = JsonSerializer.Deserialize<Customer>(json)!;
Console.WriteLine(````````ID : ${customer.Id}````````);
Console.WriteLine(````````Name : ${customer.Name}````````);
Console.WriteLine(````````Email : ${customer.Email}````````);
Console.WriteLine(````````City : ${customer.Address?.City}````````);
Console.WriteLine(````````Roles : ${string.Join(", ", customer.Roles)}````````);
// Round-trip check
string reserialised = JsonSerializer.Serialize(customer, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(````````\nRound-trip valid: ${json.Replace(" ","").Replace("\n","").Replace("\r","") == reserialised.Replace(" ","").Replace("\n","").Replace("\r","")}````````);JsonSerializer.Deserialize<T>() maps JSON properties to C# properties by name (case-insensitive by default). Nested objects and arrays are handled automatically. Raw string literals (""" """) make multi-line JSON readable.
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class ApiResponse
{
public int StatusCode { get; set; }
public string StatusMessage { get; set; } = "";
public object? Data { get; set; }
public string? ErrorMessage { get; set; }
}
// Reusable options — camelCase keys, indented, skip nulls
private static readonly JsonSerializerOptions ApiOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true, // lenient deserialization
};
var successResponse = new ApiResponse
{
StatusCode = 200,
StatusMessage = "OK",
Data = new { UserId = 1, UserName = "alice" },
ErrorMessage = null, // will be omitted
};
string json = JsonSerializer.Serialize(successResponse, ApiOptions);
Console.WriteLine("Serialized (camelCase, no nulls):");
Console.WriteLine(json);
// Deserialize using the same options (case-insensitive)
string incoming = ````````{"statusCode":404,"statusMessage":"Not Found","errorMessage":"User not found"}````````;
var errorResp = JsonSerializer.Deserialize<ApiResponse>(incoming, ApiOptions)!;
Console.WriteLine(````````\nDeserialized: ${errorResp.StatusCode} ${errorResp.StatusMessage}````````);
Console.WriteLine(````````Error: ${errorResp.ErrorMessage}````````);PropertyNamingPolicy.CamelCase converts PascalCase C# property names to camelCase JSON keys automatically. WhenWritingNull omits null properties, reducing payload size. Reusing a static options instance avoids repeated allocation.
Quick Quiz
1. Which attribute do you use to map a C# property to a different JSON key name?
2. What does setting PropertyNamingPolicy = JsonNamingPolicy.CamelCase on JsonSerializerOptions do?
3. Why should JsonSerializerOptions instances be reused (e.g., stored as static readonly) rather than created fresh each time?
4. What is the relationship between System.Text.Json and Newtonsoft.Json (Json.NET)?
Was this lesson helpful?