How to use JsonNode in C#

By FoxLearn 3/6/2025 7:48:13 AM   14
When you want to handle JSON without creating specific classes for (de)serialization, JsonNode offers a flexible alternative.

It allows you to work with JSON as a mutable DOM comprised of JsonNode objects (JsonObject, JsonArray, JsonValue), enabling you to read, write, and modify JSON efficiently.

Consider the following JSON that represents a book:

{
  "Title": "The Great Gatsby",
  "Authors": ["F. Scott Fitzgerald"],
  "PublishedYear": 1925
}

Modifying JSON with JsonNode

To modify this JSON string using JsonNode, follow these steps:

using System.Text.Json;
using System.Text.Json.Nodes;

// Load the JSON as a DOM
var bookJson = "{\"Title\":\"The Great Gatsby\",\"Authors\":[\"F. Scott Fitzgerald\"],\"PublishedYear\":1925}";
var jsonNode = JsonNode.Parse(bookJson);

// Modify the 'PublishedYear'
jsonNode["PublishedYear"] = 2023;

// Convert back to a JSON string
var jsonOptions = new JsonSerializerOptions() { WriteIndented = true };
var updatedJson = jsonNode.ToJsonString(jsonOptions);

Console.WriteLine(updatedJson);

Output:

The output will reflect the updated value of the PublishedYear property:

{
  "Title": "The Great Gatsby",
  "Authors": [
    "F. Scott Fitzgerald"
  ],
  "PublishedYear": 2023
}

If you're looking for alternatives to JsonNode when avoiding class-based (de)serialization:

  • For writing JSON, consider using an anonymous type or serializing a dictionary.
  • For reading JSON, you could use JsonDocument for fast, read-only access or deserialize into a dynamic object.

Using JsonNode, you can create JSON without raw strings:

var book = new JsonObject()
{
    ["Title"] = "The Great Gatsby",
    ["Authors"] = new JsonArray("F. Scott Fitzgerald"),
    ["Details"] = new JsonObject()
    {
        ["Genre"] = "Novel",
        ["Pages"] = 180
    }
};

// Convert to JSON string
var jsonOptions = new JsonSerializerOptions() { WriteIndented = true };
var bookJsonString = book.ToJsonString(jsonOptions);

System.IO.File.WriteAllText(@"C:\temp\book.json", bookJsonString);

This generates and writes the following content to the file:

{
  "Title": "The Great Gatsby",
  "Authors": [
    "F. Scott Fitzgerald"
  ],
  "Details": {
    "Genre": "Novel",
    "Pages": 180
  }
}

Modifying Existing JSON

You can also modify existing JSON:

var book = JsonNode.Parse(bookJson);

// Add a new property
book["Rating"] = 5;

// Modify the title
book["Title"] = "The Great Gatsby (Revised Edition)";

var jsonOptions = new JsonSerializerOptions() { WriteIndented = true };
Console.WriteLine(book.ToJsonString(jsonOptions));

Output:

The updated JSON will look like this:

{
  "Title": "The Great Gatsby (Revised Edition)",
  "Authors": [
    "F. Scott Fitzgerald"
  ],
  "PublishedYear": 1925,
  "Rating": 5
}

Removing a Property

To remove a property:

book.AsObject().Remove("Rating");

Adding to an Array

To add a new author to the Authors array:

book["Authors"].AsArray().Add("Another Author");

Console.WriteLine(book.ToJsonString(jsonOptions));

Output:

{
  "Title": "The Great Gatsby (Revised Edition)",
  "Authors": [
    "F. Scott Fitzgerald",
    "Another Author"
  ],
  "PublishedYear": 1925
}

Adding a Property Without Overwriting an Existing One

You can use the null-coalescing assignment operator (??=) to add a property only if it doesn’t already exist. This is helpful when you want to ensure existing properties remain unchanged.

Consider the following JSON representing a book:

{
  "Title": "The Great Gatsby"
}

Adding a Property

Suppose you want to add a property called Rating with a default value of 5, but you don’t want to overwrite it if it already exists. Here’s how to do that with the ??= operator:

var bookJson = "{\"Title\":\"The Great Gatsby\"}";
var book = JsonNode.Parse(bookJson);

// Add the 'Rating' property if it doesn't already exist
book["Rating"] ??= 5;

var currentRating = (int)book["Rating"];
Console.WriteLine($"Book has a rating of {currentRating}");

Output:

Book has a rating of 5

Now, if the JSON string already has the property defined as "Rating": 8, running the same code would not overwrite the property:

var bookJsonWithRating = "{\"Title\":\"The Great Gatsby\", \"Rating\":8}";
var bookWithRating = JsonNode.Parse(bookJsonWithRating);

// Attempt to add the 'Rating' property
bookWithRating["Rating"] ??= 5;

var currentRatingWithExisting = (int)bookWithRating["Rating"];
Console.WriteLine($"Book has a rating of {currentRatingWithExisting}");

Output:

Book has a rating of 8

Reading JSON

While the primary purpose of JsonNode is to modify JSON, you may also need to read values.

For instance, consider the following JSON:

{
  "Title": "The Great Gatsby",
  "Authors": ["F. Scott Fitzgerald"],
  "PublishedYear": 1925,
  "Started": "2022-01-01T00:00:00"
}

Here’s how you can read the Started property and get its value safely:

var bookWithStarted = JsonNode.Parse(bookJsonWithRating);

var started = (DateTime?)bookWithStarted["Started"];

if (started.HasValue)
{
    Console.WriteLine($"Book started being written in year {started.Value.Year}");
}
else
{
    Console.WriteLine("Start date is not available.");
}

If the Started property exists, the output will be:

Book started being written in year 2022

If the property doesn’t exist, it will display:

Start date is not available.

Handling Errors

When attempting to read a property, you may encounter casting issues. For instance, if the JSON is malformed like this:

{
  "Title": "The Great Gatsby",
  "Started": 1
}

Reading Started as a DateTime would throw an exception:

var bookWithInvalidStarted = JsonNode.Parse(bookJsonWithRating);

var startedInvalid = (DateTime?)bookWithInvalidStarted["Started"]; // This will throw an exception

Safely Attempting to Read

To avoid such exceptions, you can use TryGetValue() to safely retrieve the underlying value:

DateTime? startedSafe = null;
bookWithInvalidStarted["Started"]?.AsValue().TryGetValue(out startedSafe);

if (startedSafe.HasValue)
{
    // Use the value
}
else
{
    Console.WriteLine("Property is missing or isn't a DateTime");
}

This outputs a generic error message:

Property is missing or isn't a DateTime

Case Sensitivity

By default, JsonNode is case-sensitive. To handle properties in a case-insensitive manner, you can configure it as follows:

var jsonNodeOptions = new JsonNodeOptions()
{
    PropertyNameCaseInsensitive = true
};

var bookCaseInsensitive = JsonNode.Parse(bookJsonWithRating, jsonNodeOptions);

Console.WriteLine((int?)bookCaseInsensitive["rating"]); // This will work even if the case doesn't match

This will output the value regardless of case:

8

You can add properties conditionally, read values while modifying, and handle case sensitivity as described above. This makes JsonNode a versatile choice for working with JSON in C#.