C#

C# Fundamentals

19 lessons

Progress0%
1. Introduction to C#
1What is C#?
2. Variables and Data Types
1Data Types in C#
3. Control Flow
ConditionalsLoops
4. Methods
Defining MethodsOptional Parameters and Overloading
5. Object-Oriented Programming
Classes and PropertiesInheritanceInterfaces and Generics
6. LINQ and Async
LINQ Queriesasync/await
7. Exception Handling
try/catch/finally & Exception TypesCustom Exceptions & IDisposable
8. Delegates & Events
Delegates & LambdaEvents & Event Handlers
9. Records & Pattern Matching
Record TypesPattern Matching & Switch Expressions
10. File I/O & JSON
File & Stream OperationsJSON Serialization
All Tutorials
C#File I/O & JSON
Lesson 19 of 19 min
Chapter 10 · Lesson 2

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:

code
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:

code
var options = new JsonSerializerOptions
{
    PropertyNamingPolicy   = JsonNamingPolicy.CamelCase,
    WriteIndented          = true,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
  • PropertyNamingPolicy.CamelCase — "firstName" instead of "FirstName"
  • WriteIndented — pretty-print for human readability
  • DefaultIgnoreCondition — 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:

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

Or with streams:

code
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

Serialize Object to JSON Stringcsharp
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.

Deserialize JSON to Objectcsharp
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.

JsonSerializerOptions — camelCase and Indented Outputcsharp
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?

PreviousFinish