👉🏼 Click here to Join I ❤️ .NET WhatsApp Channel to get 🔔 notified about new articles and other updates.
Maximizing JSON Performance with Utf8JsonReader and Utf8JsonWriter in .NET

Maximizing JSON Performance with Utf8JsonReader and Utf8JsonWriter in .NET

Author - Abdul Rahman (Bhai)

JSON

5 Articles

Improve

Table of Contents

  1. What we gonna do?
  2. Why we gonna do?
  3. How we gonna do?
  4. Summary

What we gonna do?

Think JsonSerializer is fast? It is—but it's not the fastest. When you need maximum performance with minimal memory allocation, you need Utf8JsonReader and Utf8JsonWriter. In this article, we'll show you how to leverage these low-level APIs to process JSON at blazing speed.

Why we gonna do?

Here's the truth: JsonSerializer uses Utf8JsonReader and Utf8JsonWriter under the hood . These are the low-level primitives that power all the high-level serialization you use every day. So why use them directly? Two reasons: speed and control.

When you're dealing with massive JSON files (think multi-gigabyte logs or streaming API responses), the overhead of creating .NET objects adds up fast. Utf8JsonReader and Utf8JsonWriter let you process JSON token by token—reading or writing only what you need without ever allocating objects for the parts you don't care about.

These are forward-only, non-cached APIs. You read or write sequentially, which is exactly what makes them so efficient. Think of it like streaming video versus downloading the whole file—you process only what you need, when you need it.

How we gonna do?

Here's how to achieve high-performance JSON processing with Utf8JsonReader and Utf8JsonWriter:

Step 1: Understanding Utf8JsonWriter

Utf8JsonWriter is a forward-only writer for creating UTF-8 encoded JSON. You write elements sequentially—start an object or array, write properties and values, then end the object or array. It validates JSON structure as you write, so invalid JSON throws an exception (unless you disable validation).

Step 2: Writing JSON with Utf8JsonWriter

Write to a Stream, IBufferWriter<byte>, or any writable UTF-8 destination.


using System.Text.Json;

var options = new JsonWriterOptions
{
    Indented = true // Pretty-print the output
};

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

// Start writing the JSON object
writer.WriteStartObject();

writer.WriteString("userId", "12345");
writer.WriteNumber("temperature", 22);
writer.WriteBoolean("isActive", true);

// Write a nested object
writer.WritePropertyName("location");
writer.WriteStartObject();
writer.WriteString("city", "London");
writer.WriteNumber("latitude", 51.5074);
writer.WriteNumber("longitude", -0.1278);
writer.WriteEndObject();

// Write an array
writer.WritePropertyName("readings");
writer.WriteStartArray();
writer.WriteNumberValue(18);
writer.WriteNumberValue(20);
writer.WriteNumberValue(22);
writer.WriteEndArray();

writer.WriteEndObject();
writer.Flush();

// Get the JSON as a string
string json = Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(json);

/* Output:
{
  "userId": "12345",
  "temperature": 22,
  "isActive": true,
  "location": {
    "city": "London",
    "latitude": 51.5074,
    "longitude": -0.1278
  },
  "readings": [18, 20, 22]
}
*/

Step 3: Writing Null Values and Raw JSON

You can write null values and even raw JSON strings directly.


writer.WriteStartObject();

// Write a null property
writer.WriteNull("middleName");

// Write raw JSON (bypasses validation—use with caution!)
writer.WritePropertyName("metadata");
writer.WriteRawValue("""{"version": "2.0", "build": 42}""");

writer.WriteEndObject();
writer.Flush();

/* Output:
{
  "middleName": null,
  "metadata": {"version": "2.0", "build": 42}
}
*/

Step 4: Performance Best Practices for Utf8JsonWriter

  • Write UTF-8 directly: Utf8JsonWriter is optimized for UTF-8. Avoid UTF-16 conversions.
  • Reuse writers: Reset and reuse writers instead of creating new instances.
  • Flush when done: Always call Flush() to ensure data is written.
  • Disable validation for speed: Set SkipValidation = true in options if you're certain your JSON is valid and need extra speed.

Step 5: Understanding Utf8JsonReader

Utf8JsonReader is a forward-only, low-allocation reader that processes JSON token by token. It's a ref struct, which means it lives on the stack and cannot be passed by value—only by reference.

Step 6: Reading JSON with Utf8JsonReader

Read from a ReadOnlySpan<byte> or ReadOnlySequence<byte>. The reader advances token by token, and you check the TokenType to determine what you're reading.


string jsonString = """
{
  "userId": "12345",
  "temperature": 22,
  "readings": [18, 20, 22]
}
""";

byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonString);
var reader = new Utf8JsonReader(jsonBytes);

while (reader.Read())
{
    switch (reader.TokenType)
    {
        case JsonTokenType.PropertyName:
            string propertyName = reader.GetString();
            Console.WriteLine($"Property: {propertyName}");
            break;

        case JsonTokenType.String:
            string stringValue = reader.GetString();
            Console.WriteLine($"  String Value: {stringValue}");
            break;

        case JsonTokenType.Number:
            int numberValue = reader.GetInt32();
            Console.WriteLine($"  Number Value: {numberValue}");
            break;

        case JsonTokenType.StartObject:
            Console.WriteLine("Start Object");
            break;

        case JsonTokenType.EndObject:
            Console.WriteLine("End Object");
            break;

        case JsonTokenType.StartArray:
            Console.WriteLine("Start Array");
            break;

        case JsonTokenType.EndArray:
            Console.WriteLine("End Array");
            break;
    }
}

/* Output:
Start Object
Property: userId
  String Value: 12345
Property: temperature
  Number Value: 22
Property: readings
Start Array
  Number Value: 18
  Number Value: 20
  Number Value: 22
End Array
End Object
*/

Step 7: Searching for Specific Values

You can use ValueTextEquals to compare property names efficiently—it handles escaped characters automatically.


byte[] searchProperty = Encoding.UTF8.GetBytes("temperature");
var reader = new Utf8JsonReader(jsonBytes);

while (reader.Read())
{
    if (reader.TokenType == JsonTokenType.PropertyName &&
        reader.ValueTextEquals(searchProperty))
    {
        reader.Read(); // Move to the value
        int temperature = reader.GetInt32();
        Console.WriteLine($"Found temperature: {temperature}°C");
        break; // Stop searching once found
    }
}
// Output: Found temperature: 22°C

Step 8: Handling the Byte Order Mark (BOM)

If your JSON might have a BOM, remove it before reading to avoid errors.


byte[] utf8Bom = new byte[] { 0xEF, 0xBB, 0xBF };
byte[] jsonData = File.ReadAllBytes("data.json");

// Remove BOM if present
if (jsonData.Length >= 3 &&
    jsonData[0] == utf8Bom[0] &&
    jsonData[1] == utf8Bom[1] &&
    jsonData[2] == utf8Bom[2])
{
    jsonData = jsonData[3..]; // Skip the first 3 bytes
}

var reader = new Utf8JsonReader(jsonData);
// Now safe to read

Step 9: Performance Best Practices for Utf8JsonReader

  • Read UTF-8 directly: Convert UTF-16 strings to UTF-8 bytes before reading for best performance.
  • Use ValueTextEquals: For property comparisons, this method is faster and handles escaping.
  • Know your structure: Sequential searches are slow; direct navigation is faster.
  • Buffer large streams: For files over 1GB, read in chunks to avoid loading everything into memory.
  • Pass by reference: Since it's a ref struct, pass ref Utf8JsonReader to methods.

Step 10: When to Use Low-Level APIs vs JsonSerializer

Use Utf8JsonReader/Writer when:

  • You need maximum performance and minimal allocations
  • Processing huge files (multi-GB logs, streaming data)
  • You only need specific parts of a large JSON payload
  • Building custom serializers or parsers

Use JsonSerializer when:

  • You have strongly-typed classes to serialize/deserialize
  • Convenience and ease of use outweigh raw speed
  • JSON payloads are reasonably sized (under 10MB)

Summary

Utf8JsonReader and Utf8JsonWriter are the secret weapons for high-performance JSON processing in .NET. They trade convenience for speed, control, and minimal memory usage. Use them when you're processing massive files, building custom parsers, or when every millisecond and megabyte counts. For everything else, JsonSerializer has you covered—because it's using these same APIs under the hood anyway.

👉🏼 Click here to Join I ❤️ .NET WhatsApp Channel to get 🔔 notified about new articles and other updates.
  • JSON
  • Utf8JsonReader
  • Utf8JsonWriter
  • JSON
  • Performance
  • Low-level
  • System.Text.Json
  • Streaming
  • UTF-8