iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

System.Text.Json

System.Text.Json is the modern JSON library in .NET — fast, allocation-aware, and source-generator-friendly. It replaces Newtonsoft.Json (Json.NET) for new code. Use JsonSerializerOptions to control casing, default values, and converters; use the source generator for AOT and lower allocations.

Serialise, deserialise, custom converters, source-gen

EXAMPLE
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

class JsonDemo
{
    // 1) Records serialise out of the box; use JsonPropertyName for renames
    public record Order(
        [property: JsonPropertyName("id")] string Id,
        [property: JsonPropertyName("customer")] string Customer,
        [property: JsonPropertyName("totalCents")] long TotalCents,
        [property: JsonPropertyName("status")] OrderStatus Status,
        [property: JsonPropertyName("paidAt")] DateTime? PaidAt
    );

    public enum OrderStatus { New, Paid, Shipped, Cancelled }

    static readonly JsonSerializerOptions JsonOpts = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        WriteIndented = true,
        Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) },
    };

    static async Task Main()
    {
        var o = new Order("o1", "alice", 4995, OrderStatus.New, null);

        // 2) Serialise to string
        string s = JsonSerializer.Serialize(o, JsonOpts);
        Console.WriteLine(s);

        // 3) Deserialise back
        var back = JsonSerializer.Deserialize<Order>(s, JsonOpts);
        Console.WriteLine(back);

        // 4) Stream straight into a file — no intermediate string allocation
        await using (var fs = File.Create("order.json"))
            await JsonSerializer.SerializeAsync(fs, o, JsonOpts);

        await using (var rs = File.OpenRead("order.json"))
        {
            var fromFile = await JsonSerializer.DeserializeAsync<Order>(rs, JsonOpts);
            Console.WriteLine(fromFile?.Id);
        }

        // 5) Source generator — zero reflection, lower allocations, AOT-safe
        // 1. Define a partial context:
        //    [JsonSerializable(typeof(Order))]
        //    public partial class AppJsonContext : JsonSerializerContext {}
        // 2. Use it:
        //    JsonSerializer.Serialize(o, AppJsonContext.Default.Order);
        //    JsonSerializer.Deserialize(s, AppJsonContext.Default.Order);

        // 6) Polymorphic types via type discriminator (System.Text.Json 7+)
        var payment = (PaymentEvent)new CardPayment("o1", "visa", 4995);
        var json = JsonSerializer.Serialize(payment, JsonOpts);
        Console.WriteLine(json);   // { "kind": "card", "orderId": ..., "scheme": ..., "amount": ... }
        var p2 = JsonSerializer.Deserialize<PaymentEvent>(json, JsonOpts);
        Console.WriteLine(p2);

        // 7) Custom converter — money as a single string field
        var opts = new JsonSerializerOptions { Converters = { new MoneyConverter() } };
        var receipt = new Receipt("r1", new Money(4995, "AUD"));
        Console.WriteLine(JsonSerializer.Serialize(receipt, opts));   // { "id": "r1", "total": "AUD 49.95" }
    }
}

[JsonPolymorphic(TypeDiscriminatorPropertyName = "kind")]
[JsonDerivedType(typeof(CardPayment), "card")]
[JsonDerivedType(typeof(BankTransfer), "bank")]
public abstract record PaymentEvent(string OrderId);
public record CardPayment(string OrderId, string Scheme, long Amount) : PaymentEvent(OrderId);
public record BankTransfer(string OrderId, string Bsb, long Amount) : PaymentEvent(OrderId);

public record Money(long Cents, string Currency);
public record Receipt(string Id, Money Total);

public class MoneyConverter : JsonConverter<Money>
{
    public override Money Read(ref Utf8JsonReader reader, Type t, JsonSerializerOptions o)
    {
        var s = reader.GetString()!;     // "AUD 49.95"
        var parts = s.Split(' ');
        var dollars = double.Parse(parts[1]);
        return new Money((long)Math.Round(dollars * 100), parts[0]);
    }
    public override void Write(Utf8JsonWriter w, Money m, JsonSerializerOptions o)
        => w.WriteStringValue($"{m.Currency} {(m.Cents / 100.0):F2}");
}

Why it matters

For hot-path serialisation, generate context with [JsonSerializable] — the source generator skips reflection, allocates dramatically less, and works under AOT/trimming. It is the single change that turns System.Text.Json from "fast" to "as fast as any library I have benchmarked against".

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
using System.Text.Json;
string json = JsonSerializer.Serialize(new { Name = "Ada", Age = 36 });
var back = JsonSerializer.Deserialize<User>(json);
Try it Yourself »

Discussion

Loading…