System.Text.Json

Turn objects into JSON text and back with JsonSerializer, and control the property names the wire format uses.

Syntaxpublic static string Serialize<TValue>(TValue value, JsonSerializerOptions? options = null)

JSON is a text format for structured data: objects in braces, arrays in brackets, plus strings, numbers, true, false and null. Nearly every web API speaks it. Serializing means turning an object into that text; deserializing means reading the text back into an object. System.Text.Json is the built-in library for both, and it needs no package.

The two calls

JsonSerializer.Serialize(value) returns a string. JsonSerializer.Deserialize<Book>(json) returns a Book?, nullable because the text could be the word null. Only public properties with a getter are included; fields are skipped unless you set IncludeFields to true.

Options

A JsonSerializerOptions object carries the settings. PropertyNamingPolicy = JsonNamingPolicy.CamelCase writes firstName for a property called FirstName, which is what most APIs expect; SnakeCaseLower, SnakeCaseUpper, KebabCaseLower and KebabCaseUpper are also available. WriteIndented = true adds line breaks and two-space indentation. DefaultIgnoreCondition can skip members that are null.

new JsonSerializerOptions(JsonSerializerDefaults.Web) is shorthand for three settings at once: camelCase names, case-insensitive matching and numbers accepted inside quotes. ASP.NET Core uses those defaults. .NET 10 adds JsonSerializerOptions.Strict, a preset that rejects unknown and duplicate properties.

Names C# cannot carry

When the wire name does not fit C# naming, put [JsonPropertyName("isbn_13")] on the property. The attribute wins over the naming policy. [JsonIgnore] leaves a member out entirely, which is how you keep a password or an internal flag out of a response.

Build one options object and reuse it. A fresh one per call throws away the serializer's cached metadata and is noticeably slow.

Example

Example · csharp
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

JsonSerializerOptions options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

Book book = new Book { Title = "Dune", Year = 1965, Isbn = "0441013597" };

string json = JsonSerializer.Serialize(book, options);
Console.WriteLine(json);

// Deserialize returns Book? because the text could be the word null.
Book? copy = JsonSerializer.Deserialize<Book>(json, options);
Console.WriteLine(copy?.Title);
Console.WriteLine(copy?.Isbn);

public class Book
{
    public string Title { get; set; } = "";
    public int Year { get; set; }

    // The attribute wins over the camelCase policy for this one member.
    [JsonPropertyName("isbn_13")]
    public string Isbn { get; set; } = "";
}

// Output:
// {"title":"Dune","year":1965,"isbn_13":"0441013597"}
// Dune
// 0441013597

When to use it

  • Send an object as the body of an HTTP request to another service.
  • Store a settings object in a file and read it back on the next run.
  • Match an external API's property names without renaming your C# properties.
  • Keep a password or internal id out of a serialized response.

More examples

The Web preset and its longhand

Worth knowing both forms, because an ASP.NET Core endpoint already behaves like the first one and a console program does not. If JSON binds correctly inside a web API and silently comes back empty in a test or a worker, mismatched options are the usual cause.

Example · csharp
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

// Shorthand: the Web preset.
JsonSerializerOptions web = new JsonSerializerOptions(JsonSerializerDefaults.Web);

// Longhand: the same three settings written out.
JsonSerializerOptions byHand = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    PropertyNameCaseInsensitive = true,
    NumberHandling = JsonNumberHandling.AllowReadingFromString
};

// Wrong casing, and the number arrives inside quotes.
string json = """{ "itemcount": "3" }""";

Console.WriteLine(JsonSerializer.Deserialize<Cart>(json, web)?.ItemCount);
Console.WriteLine(JsonSerializer.Deserialize<Cart>(json, byHand)?.ItemCount);

public class Cart
{
    public int ItemCount { get; set; }
}

// Output:
// 3
// 3

Indenting, skipping nulls and hiding members

WriteIndented is for files and logs a person reads; leave it off for API responses, where it only adds bytes. JsonIgnore is the safety rail that keeps secrets out of a payload, and it is better than remembering to copy fields by hand, because a new property added later is not accidentally exposed.

Example · csharp
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

JsonSerializerOptions options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true,                                         // two-space indent
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull   // drop null members
};

Person person = new Person { Name = "Ada", Nickname = null, Password = "hunter2" };
Console.WriteLine(JsonSerializer.Serialize(person, options));

public class Person
{
    public string Name { get; set; } = "";
    public string? Nickname { get; set; }

    [JsonIgnore]   // never written out, never read in
    public string Password { get; set; } = "";
}

// Output:
// {
//   "name": "Ada"
// }

Reading JSON without declaring a type

Sometimes you only need one field out of a large unfamiliar payload, and writing a class for the whole shape is wasted work. JsonDocument gives read-only access to the parsed text. It owns pooled memory, so it implements IDisposable, and any JsonElement you take from it stops being valid once it is disposed.

Example · csharp
using System;
using System.Text.Json;

string json = """{ "id": 4, "tags": ["red", "blue"] }""";

// JsonDocument parses the text and lets you walk it. It must be disposed.
using JsonDocument document = JsonDocument.Parse(json);
JsonElement root = document.RootElement;

Console.WriteLine(root.GetProperty("id").GetInt32());
Console.WriteLine(root.GetProperty("tags").GetArrayLength());
Console.WriteLine(root.GetProperty("tags")[0].GetString());

// Output:
// 4
// 2
// red

Discussion

  • Be the first to comment on this lesson.