Mastering the use of System.Text.Json

Serhii Kokhan
8 min readDec 30, 2023

--

Introduction

Handling JSON data is a daily task for many developers, given its widespread use in modern applications. With the rise of .NET’s System.Text.Json library, working with JSON has never been more efficient or straightforward. In this guide, we’ll walk through the essentials of this library, ensuring you have the tools and knowledge to manage JSON data with ease. Whether you’re new to JSON or just looking to sharpen your skills, let’s dive in and see what .NET brings to the table!

Primitives

The System.Text.Json namespace in .NET equips developers with powerful classes tailored for various JSON operations, be it creation, update, deletion, or traversal. With the understanding of JsonValue, JsonObject, JsonArray, JsonDocument, and JsonNode, manipulating JSON in .NET has never been more intuitive and efficient.

JsonValue

JsonValue representing the atomic elements in the JSON, caters to the fundamental data types: numbers, strings, booleans, and null. With simple instantiation, you can encapsulate a string within the JSON paradigm. But the real beauty lies in its type safety. Implicit conversions are a cornerstone, allowing retrieval of values in their native .NET formats. While JsonValue shines for individual values, remember it’s not crafted for complex structures — that’s where our next primitive comes into play.

JsonValue number = JsonValue.Create(123);
JsonValue text = JsonValue.Create("Hello World");
JsonValue flag = JsonValue.Create(false);

Definition
Represents simple JSON values: numbers, strings, booleans, or null.

Best Practices

  • Extremely lightweight as it represents only single values.
  • Ideal for scenarios where you’re dealing with individual JSON values.
  • Ensure type safety when casting to native .NET types.

Pitfalls

  • Not suitable for representing nested or complex JSON structures.
  • Over-reliance can lead to inefficient code.
  • Performance Benchmarks
  • Extremely lightweight as it represents only single values.

JsonObject

Imagine needing a collection of key-value pairs in JSON. That’s precisely the domain of JsonObject. It’s not just a static representation; you can dynamically add, remove, or alter these pairs even after the object’s creation.

JsonObject employee = new JsonObject
{
["firstName"] = JsonValue.Create("John"),
["lastName"] = JsonValue.Create("Doe"),
["isManager"] = JsonValue.Create(false)
};

To update, simply reassign a value:

employee["isManager"] = true;

Deleting a property can be achieved using the Remove method:

employee.Remove("isManager");

Definition
Encapsulates JSON objects — key-value pairs. The methods ContainsKey and TryGetValue further enhance safety, ensuring the key’s existence before any retrieval attempts. However, for pure read-only tasks, JsonObject might be overkill.

Best Practices

  • Use for dynamic JSON creation and manipulation.
  • JsonObject provides methods like TryGetValue to safely access properties.

Pitfalls

  • Inefficient for read-only tasks. For those, consider JsonDocument.

Performance Benchmarks

  • Efficient in representing and modifying objects, but might not be as performant as JsonDocument for read-only operations.

JsonArray

Lists or sequences in JSON are encapsulated by JsonArray. Think of it as a dynamic list dedicated to JSON, capable of versatile operations.

JsonArray colors = new JsonArray { "red", "green", "blue" };

Addition and removal are simple operations:

colors.Add("black");
colors.Remove("red");

Definition

Represents sequential JSON arrays. It’s crucial, however, to ensure type consistency within these arrays. While they’re adept for list-like structures, for more demanding performance scenarios, native .NET collections might be a better choice.

Best Practices

  • When the primary data structure in JSON is an array, JsonArray is your go-to class.
  • Useful for JSON lists, think of it like a List<string> for JSON.

Pitfalls

  • For large datasets, consider performance implications. Native arrays or lists might be more efficient.

Performance Benchmarks

  • Efficient for sequential data access but can be slower for random access compared to native .NET arrays.

JsonDocument

When it’s about capturing a JSON snapshot without the intent of alteration, JsonDocument stands out. It’s a parsed, read-only rendition of the JSON content. The memory efficiency is notable since it operates within a rented buffer, reducing allocations.

using var doc = JsonDocument.Parse("{\"country\":\"Ukraine\",\"capital\":\"Kyiv\"}");
var capital = doc.RootElement.GetProperty("capital").GetString();

Definition

A read-only representation of a parsed JSON document. While its read-only nature ensures peak performance for certain tasks, it inherently means the structure is immutable. If there’s a need for modification, you might want to explore other primitives.

Best Practices

  • Ideal for inspecting or querying a JSON without the need for modifications.
  • Being read-only, it’s optimized for performance in such scenarios.

Pitfalls

  • Cannot modify the JSON structure.
  • Ensure proper disposal to free up the rented buffer memory.

Performance Benchmarks

  • High performance for read-only operations due to its rented buffer mechanism.

JsonNode

At the abstract heart of System.Text.Json is JsonNode, underlying the likes of JsonValue, JsonObject, and JsonArray. It offers a unified representation of any node within a JSON structure. While developers rarely interact with it directly, understanding its foundational role can shed light on the behavior of its derivatives. Each derivative, from value to object to array, brings a tailored functionality set, ensuring developers have the right tool for the right job.

JsonNode node = new JsonObject();
node["name"] = "Doe";

For locating a particular node or value within complex JSON structures:

if(node is JsonObject obj && obj.ContainsKey("name"))
{
Console.WriteLine($"Found name: {obj["name"]}");
}

Definition

The abstract base class for JsonValue, JsonObject, and JsonArray.

Best Practices

  • Offers flexibility and dynamic JSON manipulation capabilities.
  • Use derived types based on your specific needs.

Pitfalls

  • Being abstract, it’s essential to know its derived types for practical applications.

Performance Benchmarks

  • As it’s a base class, its performance metrics would be more relevant when considering the derived types.

Node-to-String

ToString()

By default, each .NET object offers a ToString() method. However, in the context of JSON, it serves a specialized function.

ToString() method on JSON nodes provides a string representation of the current value. For primitive values like strings and numbers, the output is as one would expect. However, for more complex objects, the output will be formatted for readability, with proper indentation and line breaks.

var simpleValue = JsonValue.Create("Alice");
Console.WriteLine(simpleValue.ToString());
// Output: Alice

var complexValue = new JsonObject
{
["id"] = 1,
["name"] = "Bob",
["tags"] = new JsonArray { "friend", "developer" }
};
Console.WriteLine(complexValue.ToString());
/* Output:
{
"id": 1,
"name": "Bob",
"tags": [
"friend",
"developer"
]
}
*/

As seen, ToString() method ensures that more intricate objects are indented, improving human readability.

ToJsonString()

While ToString() targets readability, ToJsonString() aims for a compact and valid JSON output. This is the method to employ when you’re looking for a proper JSON representation that can be transmitted over a network or stored in a database.

var simpleValue = JsonValue.Create("Alice");
Console.WriteLine(simpleValue.ToJsonString());
// Output: "Alice"

var complexValue = new JsonObject
{
["id"] = 1,
["name"] = "Bob",
["tags"] = new JsonArray { "friend", "developer" }
};
Console.WriteLine(complexValue.ToJsonString());
// Output: {"id":1,"name":"Bob","tags":["friend","developer"]}

Note that in ToJsonString(), string values are enclosed in double quotes, consistent with the JSON standard. For complex objects, spaces and line breaks are eliminated to produce a compact representation.

Customization

Beyond the basic conversion, ToJsonString() also allows for customization using JsonSerializerOptions. This is handy when you need to modify the serialized output to match certain requirements, like camel casing properties.

var data = new JsonObject
{
["FirstName"] = "Charlie",
["LastName"] = "Brown"
};

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

Console.WriteLine(data.ToJsonString(options));
// Output: {"firstName":"Charlie","lastName":"Brown"}

In the example above, the JsonSerializerOptions was used to change property names into camel case.

Tips & Tricks

System.Text.Json has established itself as a high-performance and lightweight library for JSON operations in .NET. Whether you’re new to it or have been using it for a while, some insights can optimize your usage and address common challenges.

Using Property Naming Policies

  • By default, System.Text.Json uses the property names as they are. But often, JSON properties in APIs are in snake case or other formats.
  • Use JsonPropertyNameAttribute if you want to specify a different name for a single property.
  • For global settings, leverage the PropertyNamingPolicy. For example, use JsonSerializerOptions with PropertyNamingPolicy = JsonNamingPolicy.CamelCase to handle camel case JSON properties automatically.

Handling Null Values

  • If you want to exclude properties with null values from being serialized, set the DefaultIgnoreCondition property:
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

Custom Converters for Complex Types

  • Sometimes, built-in serialization might not suffice, especially with complex types.
  • Implement JsonConverter<T> to dictate how a type should be serialized and deserialized. Once implemented, attach it to your class using the JsonConverterAttribute or add it to the Converters collection in JsonSerializerOptions.

Opt for ReadOnlySpan<byte> for Performance

  • When reading large JSON payloads, use ReadOnlySpan<byte> or ReadOnlyMemory<byte> instead of string for better performance since they avoid additional allocations.

Using Utf8JsonReader for Streaming Scenarios

  • Utf8JsonReader is a high-performance, forward-only reader. It’s perfect for streaming scenarios or reading large datasets, ensuring minimal memory overhead.

JsonSerializerOptions for Global Settings

  • Instead of setting serialization options each time, configure a global JsonSerializerOptions object and reuse it. This ensures consistency and reduces repetitive code.

Case Sensitivity

  • By default, deserialization is case-sensitive. If you wish to change this behavior, adjust the PropertyNameCaseInsensitive property in JsonSerializerOptions.

Always Dispose JsonDocument

  • JsonDocument should always be disposed of to release the rented memory buffers. A common approach is to use the using statement.

System.Text.Json vs Newtonsoft.Json

System.Text.Json and Newtonsoft.Json are two popular libraries for handling JSON in .NET. Both have their strengths and weaknesses, which make them more or less suitable for certain scenarios.

Origin & Popularity

  • Newtonsoft.Json: Released by James Newton-King, it quickly became the de facto standard for JSON handling in .NET due to its rich feature set and flexibility.
  • System.Text.Json: Introduced by Microsoft in .NET Core 3.0 as a built-in library, aiming for better performance and tighter integration with the core framework.

Performance

  • Newtonsoft.Json: Generally considered slower than System.Text.Json because it provides a wider feature set, leading to some overhead.
  • System.Text.Json: Designed for performance from the ground up. Typically offers higher throughput and uses less memory, especially in I/O-bound operations.

API & Features

  • Newtonsoft.Json: Offers a rich and wide-ranging API. It supports features like JSONPath, comments in JSON, various serialization settings, and the ability to handle complex scenarios.
  • System.Text.Json: Has a more streamlined API targeting common use cases. Initially, it lacked some features present in Newtonsoft.Json, but many gaps have been addressed in subsequent releases.

Flexibility

  • Newtonsoft.Json: Known for its flexibility. Provides numerous ways to shape the JSON output, manage serialization/deserialization, and handle special cases.
  • System.Text.Json: More opinionated in its approach to handling JSON to ensure performance. However, this can sometimes mean less flexibility in edge cases.

Integration

  • Newtonsoft.Json: Given its long history, many third-party libraries and platforms have built-in support for it.
  • System.Text.Json: Being newer, its ecosystem integration is growing. With Microsoft’s push, more libraries and platforms are beginning to support it natively.

Migration

  • Newtonsoft.Json: Those with existing projects using it might find it cumbersome to migrate to System.Text.Json due to differences in APIs and behaviors.
  • System.Text.Json: While transitioning from Newtonsoft.Json might have challenges, starting a new project with System.Text.Json can be straightforward.

Target Framework

  • Newtonsoft.Json: Available for full .NET Framework, .NET Core, and other platforms.
  • System.Text.Json: Native to .NET Core and available in subsequent versions.

Conclusion

Choosing between System.Text.Json and Newtonsoft.Json should be based on the specific needs of a project. If performance is a primary concern and the feature set of System.Text.Json meets the project’s requirements, then it might be the better choice. On the other hand, projects that require a wide range of features, extensive customization, or have existing dependencies on Newtonsoft.Json might be more appropriate.

Summary

System.Text.Json offers a comprehensive suite of tools for JSON handling in .NET. By understanding the nuances and best-fit scenarios for each class, developers can write efficient, clean, and high-performing code for all their JSON needs.

--

--

Serhii Kokhan

Microsoft MVP | Software Architect | Stripe Certified Professional Developer | https://linktr.ee/serhiikokhan