Skip to Content
Course content

207: Utf8JsonReader and Utf8JsonWriter

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've spent a lot of time reviewing performance-critical code, and there is one misconception that keeps popping up when developers move into the high-performance side of System.Text.Json: they think Utf8JsonReader and Utf8JsonWriter are just "faster versions" of the JsonSerializer that they can swap in with minimal effort.

The "Drop-in Replacement" Myth

If you think you can just replace JsonSerializer.Deserialize<T> with a reader and get the same result with less CPU usage, you're in for a surprise. The serializer is a high-level abstraction; it handles the mapping of JSON keys to class properties, manages object instantiation, and handles type conversion for you.

The Utf8JsonReader, however, is a low-level, forward-only, non-cached cursor. It doesn't know what a "User" object is. It only knows that it just encountered a StartObject, followed by a PropertyName, followed by a String. If you try to use it like a serializer, you'll find yourself writing a massive, fragile while loop with a dozen if statements just to populate a single POCO. It's not a replacement; it's a different tool entirely.

Manual Control for Maximum Throughput

So, why would you ever put yourself through that manual labor? Because Utf8JsonReader operates directly on ReadOnlySpan<byte>. It doesn't need to convert the JSON bytes into a C# string before processing them. This eliminates huge amounts of memory allocation.

Imagine you have a 100MB JSON log file containing thousands of telemetry entries, but you only need to find the ErrorCode of entries where Severity is "Critical". Using the serializer, you'd either have to load the whole thing into memory or use a stream that still creates thousands of temporary objects. With the reader, you just skip through the bytes until you hit the keys you care about.

// Here is the pattern you'll actually use
ReadOnlySpan<byte> jsonUtf8Bytes = ...; // Your JSON data
var reader = new Utf8JsonReader(jsonUtf8Bytes);

while (reader.Read())
{
    if (reader.TokenType == JsonTokenType.PropertyName && reader.ValueTextEquals("ErrorCode"))
    {
        reader.Read(); // Move to the value
        int code = reader.GetInt32();
        Console.WriteLine($"Found error: {code}");
    }
}

Notice the ValueTextEquals method. I love this method because it compares the UTF-8 bytes directly to the string literal without allocating a new string for the property name. It's a tiny detail that makes a massive difference when you're processing millions of tokens.

Writing Without the Overhead

The Utf8JsonWriter works on the opposite principle. Instead of taking a complex object and reflecting over its properties to decide what to write, you tell the writer exactly what to emit, byte by byte.

It is designed to write directly to a BufferWriter or a Stream. This means you can construct a JSON response for a web API without ever creating a temporary "ResponseDTO" object in memory. You just pipe the data directly to the network buffer.

using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);

writer.WriteStartObject();
writer.WriteString("status", "success");
writer.WriteNumber("timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds());
writer.WriteStartArray("data");
    writer.WriteNumber(101);
    writer.WriteNumber(202);
writer.WriteEndArray();
writer.WriteEndObject();

writer.Flush();

It feels verbose, I know. But by removing the reflection and the intermediate object allocations, you're removing the primary bottlenecks that cause Garbage Collection (GC) pauses in high-traffic applications. Use the serializer for 95% of your app, but when you hit that one hot path that's killing your latency, this is where you reach for the reader and writer.




📋 Practical Task

Filtering High-Temperature Sensor Logs with Utf8JsonReader

You are building a monitoring tool that processes a large stream of sensor data. The data comes as a JSON array of objects, each containing a "sensorId" (string) and a "temp" (double). To save memory, you must not deserialize the entire array into a List.

Your Task: Write a method that takes a ReadOnlySpan<byte> of JSON data and uses Utf8JsonReader to find and print only the sensorId of sensors where the temp is greater than 100.0.

Example Input:
[{"sensorId": "A1", "temp": 98.5}, {"sensorId": "B2", "temp": 105.2}, {"sensorId": "C3", "temp": 110.0}]

Expected Output:
B2
C3

Requirements:

  • Use Utf8JsonReader to iterate through the tokens.
  • Use ValueTextEquals to check for property names.
  • Do not use JsonSerializer or JsonDocument.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.