JavaScript Object Notation (JSON) is a great way of storing configuration settings for a .NET application. JSON is also an efficient method to transfer data from one machine to another. JSON is easy to create, is human readable, and is easy for programming languages to parse and generate. This text-based format for representing data is language agnostic and thus easy to use in C#, JavaScript, Python, and almost any programming language existing today. In this article, you're going to learn multiple methods of creating and manipulating JSON documents in .NET 8. In addition, you'll learn how to serialize and deserialize C# objects to and from JSON.

JSON Structure

As shown in Figure 1, a JSON object is made up of a collection of name/value pairs. You may hear these also expressed as key/value pairs, or, in C# terms, this is a property and a value. In C#, a JSON object is the equivalent of an object, record, or a struct with property names, and the values you assign to those properties. JSON can also be a collection of one or more objects (Figure 2 and Figure 3). In C#, this would be the equivalent of a dictionary, hash table, keyed list, or associative array. Although I'm going to use C# in this article, the constructs mentioned are universal across all modern programming languages. Because of this, any language can manipulate these JSON objects easily.

Figure 1: JSON objects are made up of name/value pairs.
Figure 1: JSON objects are made up of name/value pairs.

A JSON object begins with a left brace and ends with a right brace (see Figure 1). Each name is followed by a colon and the name/value pairs are separated by a comma. Each name must be wrapped within double quotes. All string values must be wrapped within double quotes.

A JSON array is an ordered collection of values that begins with a left bracket and ends with a right bracket. Each value within the array is separated by a comma. The values within the array may be a single item, such as a string or a number (Figure 2), or each value within the array may be a JSON object (Figure 3).

Figure 2: JSON arrays can have just simple values as their elements.
Figure 2: JSON arrays can have just simple values as their elements.
Figure 3: Each element in a JSON array can be a JSON object.
Figure 3: Each element in a JSON array can be a JSON object.

Nested Objects

Each value after the name can be one of the various data types shown in Table 1. Although this isn't a large list of data types, all data can be expressed with just this set of types.

Look at Figure 4 to see an example of a JSON object that has a value of each of the data types for each name. The name property has a string value “John Smith” enclosed within double quotes. The age property is a numeric with a value of 31. The ssn property is empty as represented by the keyword null. The address property is another JSON object and is thus enclosed by curly braces. The phoneNumbers property is a JSON array, with each value of the array another JSON object. As you see, JSON is very flexible and can represent almost any type of structure you can possibly need in your programming tasks.

Figure 4: Each element in a JSON object can be another object, or an array.
Figure 4: Each element in a JSON object can be another object, or an array.

JSON Manipulation Classes

There are several classes within a couple of namespaces in .NET 8 that you use to work with JSON in your C# applications. The first namespace is System.Text.Json and the second namespace is System.Text.Json.Nodes. You have most likely already used the JsonSerializer class to serialize C# objects to JSON, or to deserialize a JSON string into a C# object. However, there are other classes you can use to add nodes to a JSON object, set and retrieve values, and create new JSON objects.

The System.Text.Json Namespace

Within this namespace are classes and structures to help you manipulate JSON documents including the JsonSerializer class. Table 2 provides a description of each of the classes within this namespace. Each of these classes is illustrated throughout this article. All the classes and structures within this namespace are immutable. This means that once they've been instantiated with data, they cannot be modified in any way.

The System.Text.Json.Nodes Namespace

The classes in this namespace (Table 3) are for creating and manipulating in-memory JSON documents using a DOM. These classes provide random access to JSON elements, allow adding, editing, and deleting elements, and can convert dictionary and key value pairs into JSON documents.

Build In-Memory JSON Documents

I recommend following along with this article to ensure that you have a solid foundation for manipulating JSON documents. The tools needed to follow the step-by-step instructions in this article are .NET 6, 7, or 8, and either Visual Studio 2022 or VS Code. Create a console application named JsonSamples. The goal of this first example is to create the following JSON object.

{
  "name": "John Smith",
  "age": 31
}

Of course, there are many ways to create this JSON object using C# and the JSON classes. For this first example, open the Program.cs file, delete any lines of code in this file, and add the following Using statement as the first line.

using System.Text.Json.Nodes;

Below the Using statement, create a variable named jo that is an instance of a JsonObject. The JsonObject provides the ability to add, edit, and delete nodes within the JSON document.

JsonObject jo = new();

Add two name/value pairs to the JsonObject using the C# KeyValuePair class, as shown in the following code.

jo.Add(new KeyValuePair<string, JsonNode?>("name", "John Smith"));
jo.Add(new KeyValuePair<string, JsonNode?>("age", 31));

Write out the JSON document using the ToString() method on the JsonObject.

Console.WriteLine(jo.ToString());

The output from this statement is the JSON object shown earlier. Notice how the JSON is nicely formatted. If you wish to remove all of the carriage returns, line feeds, and whitespace between all the characters, change the call from the ToString() method to the ToJsonString() method instead. You should then see the following JSON appear in the console window.

{"name":"John Smith","age":31}

Use a New C# 12 Feature

In C# 12 (.NET 8) you can create the JsonObject using the following syntax. Note that the square brackets used in the code are a new C# 12 feature that allows you to initialize the new JsonObject object without using the new keyword.

JsonObject jo =
[
    new KeyValuePair<string, JsonNode?>("name", "John Smith"),
    new KeyValuePair<string, JsonNode?>("age", 31)
];

Using a Dictionary Class

You can pass an instance of a Dictionary<string, JsonNode?> to the constructor of the JsonObject to create your JSON document. Replace the code in the Program.cs file with the following:

using System.Text.Json.Nodes;

Dictionary<string, JsonNode?> dict = new() {
    ["name"] = "John Smith",
    ["age"] = 31
};

JsonObject jo = new(dict);

Console.WriteLine(jo.ToString());

Using a JsonValue Object

The Add() method on the JsonObject class also allows you to pass in the name and a JsonValue object. Pass in the value to static method Create() on the JsonValue class to create a new JsonValue object.

using System.Text.Json.Nodes;

JsonObject jo = new() {
    { "name", JsonValue.Create("John Smith") },
    { "age", JsonValue.Create(31) }
};

Console.WriteLine(jo.ToString());

Because the JsonObject can be initialized with a Dictionary object, you may use the same syntax you used to create a Dictionary object in the constructor of the JsonObject, as shown in the following code:

using System.Text.Json.Nodes;

JsonObject jo = new() {
  ["name"] = "John Smith",
  ["age"] = 1
};

Console.WriteLine(jo.ToString());

Create Nested JSON Objects

Not all JSON objects are simple name/value pairs. Sometimes you need one of the properties to be another JSON object. The address property is another JSON object that has its own set of name/value pairs, as shown in the following snippet:

{
    "name": "John Smith",
    "age": "31",
    "ssn": null,
    "isActive": true,
    "address": {
        "street": "1 Main Street",
        "city": "Nashville",
        "stateProvince": "TN",
        "postalCode": "37011"
    }
}

To create the above JSON object, create a new instance of a JsonObject and, using a Dictionary object, build the structure you need, as shown in the following code snippet:

using System.Text.Json.Nodes;

JsonObject jo = new() {
    ["customer"] = "Acme",
    ["IsActive"] = true,
    ["address"] = new JsonObject() {
        ["street"] = "123 Main Street",
        ["city"] = "Walla Walla",
        ["stateProvince"] = "WA",
        ["postalCode"] = "99362",
        ["country"] = "USA"
    }
};

Console.WriteLine(jo.ToString());

Parse JSON Strings into Objects

JSON documents are commonly stored as strings in a file or in memory. Instead of attempting to read specific values in the JSON using File IO or string parsing, you can parse the string into a JsonNode object. Once in this object, it's very easy to retrieve single values, or entire nodes.

Instead of repeating the same set of JSON strings throughout this article, I'm creating a single class (Listing 1) with some string constants to represent different JSON documents. The PERSON constant is a JSON document that contains four properties to represent a single person. The PHONE_NUMBERS constant is a JSON array of a few phone numbers where each number has a type property with a value such as Home, Mobile, or Work. The PERSON_ADDRESS constant contains a nested address property that is another object with a street, city, state, and postalCode properties. The PERSON_ADDRESS_PHONES constant contains person information, address information, and an array of phone numbers all in one JSON object.

Listing 1: To save typing, I have created a class with some JSON documents

namespace JsonSamples;

public class JsonStrings
{
  public const string PERSON =
    @"{
           ""name"": ""John Smith"",
           ""age"": 31,
           ""ssn"": null,
           ""isActive"": true
    }";

  public const string PHONE_NUMBERS =
    @"[
          {
               ""type"": ""Home"",
               ""number"": ""615.123.4567""
          },
          {
               ""type"": ""Mobile"",
               ""number"": ""615.345.6789""
          },
          {
               ""type"": ""Work"",
               ""number"": ""615.987.6543""
          }
    ]";

  public const string PERSON_ADDRESS =
    @"{
          ""name"": ""John Smith"",
          ""age"": 31,
          ""ssn"": null,
          ""isActive"": true,
          ""address"": {
               ""street"": ""1 Main Street"",
               ""city"": ""Nashville"",
               ""state"": ""TN"",
               ""postalCode"": ""37011""
          }
    }";

  public const string PERSON_ADDRESS_PHONES =
    @"{
          ""name"": ""John Smith"",
          ""age"": 31,
          ""ssn"": null,
          ""isActive"": true,
          ""address"": {
               ""street"": ""1 Main Street"",
               ""city"": ""Nashville"",
               ""state"": ""TN"",
               ""postalCode"": ""37011""
          },
          ""phoneNumbers"": [
          {
                ""type"": ""Home"",
                ""number"": ""615.123.4567""
          },
          {
                ""type"": ""Mobile"",
                ""number"": ""615.345.6789""
          },
          {
                ""type"": ""Work"",
                ""number"": ""615.987.6543""
          }
          ]
    }";
}

Create a JsonNode Object

The JsonNode class is probably the class you will use most often. It's very flexible and contains most of the functionality you need when manipulating JSON documents. For example, to parse the PERSON constant into a JsonNode object, place the following code into the Program.cs file:

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

// Parse string into a JsonNode object
JsonNode? jn = JsonNode.Parse(JsonStrings.PERSON);

Console.WriteLine(jn!.ToString());
Console.WriteLine(jn!.GetValueKind());

Run the console application and you should see the following displayed in the console window:

{
   "name": "John Smith",
   "age": 31,
   "ssn": null,
   "isActive": true
}
Object

The first Console.WriteLine() statement emits the JSON object, and the second Console.WriteLine() statement reports the kind of object contained in the JsonNode object that's the type of Object. Of course, you may pass to the Parse() method any of the other constant strings. Write the following code in the Program.cs file to parse the PHONE_NUMBERS constant into a JsonNode object:

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

// Parse string into a JsonNode object
JsonNode? jn = JsonNode.Parse(JsonStrings.PHONE_NUMBERS);

Console.WriteLine(jn?.ToString());
Console.WriteLine(jn!.GetValueKind());

Run the console application and you should see the following displayed in the console window:

[
  {
     "type": "Home",
     "number": "615.123.4567"
  },
  {
     "type": "Mobile",
     "number": "615.345.6789"
  },
  {
     "type": "Work",
     "number": "615.987.6543"
  }
]
Array

Notice that in this case, the GetValueKind() method reports this as an Array instead of an Object. When the JSON string that is read in starts with a square bracket, it's a JSON array instead of a JSON object.

Create a JsonDocument Object

Earlier in this article, you learned that you could add items to a JsonObject in its constructor. There's no constructor for the JsonDocument object, so you must use the Parse() method to get valid JSON data into this object. The JsonDocument object is a very efficient object to use when all you need to do is to read data from a JSON document. Once the data is parsed into the JsonDocument, access the RootElement property to retrieve the JSON. Write the following code into the Program.cs file:

using JsonSamples;
using System.Text.Json;

// Parse string into a JsonDocument object
using JsonDocument jd = JsonDocument.Parse(JsonStrings.PERSON);

// Get Root JsonElement structure
JsonElement je = jd.RootElement;

Console.WriteLine(je.ToString());
Console.WriteLine(je.ValueKind);

In the code above, you retrieve the RootElement property and place it into a new variable of the type JsonElement. It's this class that you're going to use to read the data from JSON document, as you shall soon learn. Run the application and you should see the following displayed in the console window:

{
   "name": "John Smith",
   "age": 31,
   "ssn": null,
   "isActive": true
}
Object

Read Data from JSON Documents

There are a few different ways you can read individual name/value pairs from a JSON document. Both the JsonElement and the JsonNode classes allow you to get at the data within the JSON.

Once you've parsed some JSON into a JsonDocument object, you must always use the RootElement property to retrieve specific values within the JSON document. You can either place the RootElement property into a JsonElement object, or you can use the full path of the JsonDocument.RootElement property. Write the code shown in Listing 2 into the Program.cs file.

Listing 2: Retrieve values from the JSON using the RootElement property

using JsonSamples;
using System.Text.Json;

// Parse string into a JsonDocument object
using JsonDocument jd = JsonDocument.Parse(JsonStrings.PERSON);

// Get a specific property from JSON
JsonElement je = jd.RootElement!.GetProperty("name");

// Get the numeric value from the JsonElement
Console.WriteLine($"Name={je!.GetString()}");
Console.WriteLine($"Age={jd.RootElement!
    .GetProperty("age")!.GetInt32()}");

In Listing 2, you parse the PERSON string into a JsonDocument. You then get the property called "name" from the JSON document and place this into a JsonElement object named je. Because the name property is a string value, call the GetString() method on the je variable to extract the value from the "name" property. If you don't wish to use a separate variable, you may access the jd.RootElement property directly by calling the GetProperty("age") to get the "age" element. Call the GetInt32() method on this element to extract the "age" value and display it on the console. Run the application and you should see the following output from Listing 2 displayed in the console window:

Name=John Smith
Age=31

Retrieve Data in a Nested Object

Look back at Listing 1 and notice that the PERSON_ADDRESS constant is the one with the nested "address" object. To access the "city" value within the address, replace the code in the Program.cs file with the following:

using JsonSamples;
using System.Text.Json;

// Parse string into a JsonDocument object
using JsonDocument jd = JsonDocument.Parse(JsonStrings.PERSON_ADDRESS);
 
// Get a specific property from JSON
JsonElement je = jd.RootElement.GetProperty("address").GetProperty("city");

// Get the string value from the JsonElement
Console.WriteLine($"City={je!.GetString()}");

After parsing the string into the JsonDocument object, access the RootElement property and call the GetProperty("address") to get to the “address” property, and then call GetProperty("city") to get to the “city” property. Once you have this element in a JsonElement object, call the GetString() method to retrieve the value for the "city" property.

Run the application and you should see the following displayed in the console window:

City=Nashville

Reading Data Using JsonNode

Unlike the JsonDocument object, the JsonNode object has an indexer that allows you to specify the name in double brackets to retrieve that specific node, as shown in the following code:

// Parse string into a JsonNode object
JsonNode? jn = JsonNode.Parse(JsonStrings.PERSON);

// Get the age node
JsonNode? node = jn!["age"];

With this new JsonNode object, node, retrieve the value as a JsonValue using the AsValue() method. With the JsonValue object, you can report the path of where this value came from, the type (string, number, Boolean, etc.), and get the value itself as shown in the following code:

// Get the value as a JsonValue
JsonValue value = node!.AsValue();

Console.WriteLine($"Path={value.GetPath()}");
Console.WriteLine($"Type={value.GetValueKind()}");
Console.WriteLine($"Age={value}");

Another option is to retrieve the value using the GetValue<T>() method, as shown in the following code:

// Get the value as an integer
int age = node!.GetValue<int>();
Console.WriteLine($"Age={age}");

If you type in the above code snippets into the Program.cs file and run the application, the following should be displayed in the console window:

Path=$.age
Type=Number
Age=31
Age=31

Retrieve Data in a Nested Object

Look back at Listing 1 and look at the PERSON_ADDRESS constant. This JSON string is the one with the nested "address" object. To access the "city" value within the address, you replace the code in the Program.cs file with the following:

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

// Parse string into a JsonNode object
JsonNode? jn = JsonNode.Parse(JsonStrings.PERSON_ADDRESS);

// Get the address.city node
JsonNode? node = jn!["address"]!["city"];

// Display string value from the JsonNode
Console.WriteLine($"City={node!.AsValue()}");

The above code parses the PERSON_ADDRESS string into a JsonNode object. It then uses an indexer on the jn variable to drill down to the address.city node. This node is placed into a new JsonNode object named node. The value of the city property is retrieved using the AsValue() method and displayed on the console window when you run this application.

Add, Edit, and Delete Nodes

To add a new name/value pair to a JSON document, create a JsonObject object out of the PERSON JSON string constant and convert it to a JsonObject using the AsObject() method. Once you have a JsonObject, use the Add() method to create a new name/value pair, in this case "hairColor": "Brown".

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

// Parse string into a JsonObject
JsonObject? jo = JsonNode.Parse(JsonStrings.PERSON)?.AsObject();
jo?.Add("hairColor", JsonValue.Create("Brown"));

Console.WriteLine(jo?.ToString());

Replace the code in the Program.cs file with the code listed above and run the application to see the following displayed in the console window:

{
   "name": "John Smith",
   "age": 31,
   "ssn": null,
   "isActive": true,
   "hairColor": "Brown"
}

Updating a Node

Use the JsonNode object to update a value in a name/value pair. Parse the JSON into a JsonNode object, then access the name using an indexer. Set the value using the equal sign just as you would any normal assignment in .NET.

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

// Parse string into a JsonNode object
JsonNode? jo = JsonNode.Parse(JsonStrings.PERSON);
jo!["age"] = 42;
Console.WriteLine(jo?.ToString());

Replace the code in the Program.cs file with the code listed above and run the application to see the following code displayed in the console window. Notice that the age value has changed from 31 to 42.

{
   "name": "John Smith",
   "age": 42,
   "ssn": null,
   "isActive": true
}

Deleting a Node

The Remove() method on a JsonObject is used to delete a name/value pair from a JSON document. Create a JsonObject object out of the JSON PERSON string constant. Once you have a JsonObject, use the Remove() method, passing in the name you wish to remove from the JSON. In the code below, you remove the "age" name/value pair:

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

// Parse string into a JsonObject
JsonObject? jo = JsonNode.Parse(JsonStrings.PERSON)?.AsObject();

jo?.Remove("age");

Console.WriteLine(jo?.ToString());

Replace the code in the Program.cs file with the code listed above and run the application to see the following displayed in the console window. The "age": 31 name/value pair has been removed from the JSON document.

{
  "name": "John Smith",
  "ssn": null,
  "isActive": true
}

Working with Arrays

In addition to a simple object, JSON can contain arrays of strings, numbers, Booleans, and JSON objects. Instead of using the JsonObject to represent a JSON document, use the JsonArray class to represent a list of items. For example, to create an array of string values, replace the code in the Program.cs file with the following:

using System.Text.Json.Nodes;

JsonArray ja = [ "John", "Sally", "Charlie"];

Console.WriteLine(ja.ToString());
Console.WriteLine(ja.GetValueKind());

Run the application and you should see the following code displayed in the console window. Notice that after the JSON array is displayed, the type reported from the call to the GetValueKind() method is "Array".

[
   "John",
   "Sally",
   "Charlie"
]
Array

To create an array of JSON objects, use the same syntax with the square brackets, but create a new JsonObject object separated by commas for each element you wish to create in the array. Write the following code into the Program.cs file:

using System.Text.Json.Nodes;

JsonArray ja = [
    new JsonObject() {
        ["name"] = "John Smith",
        ["age"] = 31
    },
    new JsonObject() {
        ["name"] = "Sally Jones",
        ["age"] = 33
    }
];

Console.WriteLine(ja.ToString());
Console.WriteLine(ja.GetValueKind());

Run the application and you should see the following displayed in the console window:

[
  {
     "name": "John Smith",
     "age": 31
  },
  {
     "name": "Sally Jones",
     "age": 33
  }
]
Array

Manipulate an Array

Like most arrays in .NET, you can easily add and remove elements within the array. Given the previous JsonArray object declaration, you can insert a new entry into the array by adding the following code after the declaration. The Insert() method lets you specify where in the array you wish to add the new object. In this case, you are adding a new element into the first position of the array.

ja.Insert(0, new JsonObject() {
    ["name"] = "Charlie Chaplin",
    ["age"] = "50"
});

You can always create a new JsonObject first, initialize it with some data, then add that new JsonObject to the JsonArray using the Add() method. The Add() method adds the JsonObject to the end of the array.

JsonObject jo = new() {
    ["name"] = "Buster Keaton",
    ["age"] = 55
};
ja.Add(jo);

Array elements may be removed by either a reference to the actual object, or by using an index number, as shown in the following two lines of code:

ja.Remove(jo);
ja.RemoveAt(2);

Extract a JSON Array from a JSON Node

Look back at Listing 1 to view the PERSON_ADDRESS_PHONES constant string. Within this JSON object, there's an object named "phoneNumbers" that contains an array of phone number objects. To extract the phone number objects from this string, you first need to parse the string into a JsonNode object. You then retrieve the value from phoneNumbers object and convert it into a JsonArray object using the AsArray() method, as shown in the following code:

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

// Parse string into a JsonNode object
JsonNode? jn = JsonNode.Parse(JsonStrings.PERSON_ADDRESS_PHONES);

// Get the Phone Numbers Array
JsonArray? ja = jn!["phoneNumbers"]!.AsArray();

Console.WriteLine(ja.ToString());
Console.WriteLine(ja.GetValueKind());

Place this code into the Program.cs file and run the application to display the following in the console window:

[
  {
      "type": "Home",
      "number": "615.123.4567"
  },
  {
      "type": "Mobile",
      "number": "615.345.6789"
  },
  {
      "type": "Work",
      "number": "615.987.6543"
  }
]
Array

Iterate Over Array Values Using JsonNode

Once you have a JsonArray object, you may iterate over each value in the array to extract the different property values. In the code shown below, you parse the PHONE_NUMBERS string constant into a JsonNode object. Next, convert this JsonNode object into a JsonArray using the AsArray() method. Use a foreach loop to iterate over each element in the array and emit the "type" and "number" properties onto the console window.

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

// Parse string into a JsonNode object
JsonNode? jn = JsonNode.Parse(JsonStrings.PHONE_NUMBERS);

JsonArray? nodes = jn!.AsArray();
foreach (JsonNode? node in nodes) {Console.WriteLine($"Type={node!["type"]}, 
    Phone Number = {node!["number"]}");
}

Type the above code into the Program.cs file and run the application to see the following values displayed in the console window:

Type=Home, Phone Number=615.123.4567
Type=Mobile, Phone Number=615.345.6789
Type=Work, Phone Number=615.987.6543

Iterate Over Array Values Using JsonDocument

If you wish to use the JsonDocument class instead of a JsonNode class, the following code illustrates the differences between the two classes. After parsing the PHONE_NUMBERS string constant into a JsonDocument, convert the RootElement property, which is an array, into an ArrayEnumerator using the EnumerateArray() method. You may now iterate over the array of JsonElement objects and display the phone number type and the phone number itself.

using JsonSamples;
using System.Text.Json;

// Parse string into a JsonDocument object
using JsonDocument jd = JsonDocument.Parse(
    JsonStrings.PHONE_NUMBERS);

JsonElement.ArrayEnumerator elements = jd.RootElement.EnumerateArray();
foreach (JsonElement elem in elements) {
    Console.WriteLine($"Type={elem.GetProperty("type")},
    Phone Number={elem.GetProperty("number")}");
}

The output is the same as the output shown above when using the JsonNode object to iterate over the array values.

Get a Single Phone Number from Array

Looking back at Listing 1, you see the PHONE_NUMBERS string constant, which is a JSON array. After you parse this data into a JsonNode, you might wish to retrieve just the home phone number from this array. After converting the phone numbers to a JsonArray object, use the FirstOrDefault() method to search where the "type" value is equal to "Home". If this node is found, extract the number value to display on the console window, as shown in Listing 3. If you type the code shown in Listing 3 into the Program.cs file and run the application, the code displays "Home Number=615.123.4567" in the console window.

Listing 3: Retrieve a single item from an array

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

string? value = string.Empty;

// Create JsonNode object from Phone Numbers
JsonNode? jn = JsonNode.Parse(JsonStrings.PHONE_NUMBERS);

// Cast phone numbers as an array
JsonArray ja = jn!.AsArray();

// Search for Home number
JsonNode? tmp = ja.FirstOrDefault(row => (string)(row!["type"]!
  .GetValue<string>()) == "Home");

// Extract the home number value
value = tmp!["number"]!.GetValue<string>();

Console.WriteLine($"Home Number={value}");

Parsing JSON from a File

There are a couple of different methods you may use to extract JSON from a file. You can use .NET File I/O classes, or you can use the IConfiguration interface. Let's start by looking at how you can read JSON values using the .NET File I/O classes.

Read Runtime Configuration Settings

When you run a .NET application, there's a runtimeconfig.json file (Listing 4) created with information about the application. The JSON object shown below is an example of what's generated from a console application. When you run an ASP.NET web application, there will be additional information in this file.

Listing 4: A sample runtime configuration file

{
    "runtimeOptions": {
        "tfm": "net8.0",
        "framework": {
            "name": "Microsoft.NETCore.App", "version": "8.0.0"
        },
      "configProperties": {
          "System.Runtime...": false
      }
  }
}

If you wish to read the .NET Framework version from this file, you need to first open the file and parse the text into a JsonNode or JsonDocument object, as shown in Listing 5. You then access the runtimeOptions.framework.version property to retrieve the value "8.0.0". Replace all the code in the Program.cs file with the code shown in Listing 5 and run the application to display the runtime version.

Listing 5: Use File I/O to read a value from the runtime configuration file

using System.Text.Json.Nodes;

string? value = string.Empty;
string fileName = $"{AppDomain.CurrentDomain.FriendlyName}
    .runtimeconfig.json";

if (File.Exists(fileName)) {
  JsonNode? jn = JsonNode.Parse(File.ReadAllText(fileName));
  value = jn!["runtimeOptions"]
      !["framework"]
      !["version"]?.GetValue<string>();
}

Console.WriteLine(json);

Create an appsettings.json File

In most .NET applications you write, you most likely will need a file to store global settings such as a connection string, logging levels, and other application-specific settings. This information is generally stored in a file named appsettings.json. Add this file to the console application and put the settings shown in Listing 6 into this file. Once the file is created, click on the file and bring up the Properties window. Set the Copy to Output Directory property to Copy always. You should put the connection string all on one line. I had to break it across several lines due to the formatting constraints of the print magazine.

Listing 6: Create an application settings file in the console application

{
  "ConnectionStrings": {
      "DefaultConnection": "Server=Localhost;
         Database=AdventureWorks;
         Trusted_Connection=Yes;
         MultipleActiveResultSets=true;
         TrustServerCertificate=True;"
  },
  "AdvWorksAppSettings": {
      "ApplicationName": "Adventure Works",
  },
  "Logging": {
      "LogLevel": {
          "Default": "Information", "Microsoft.AspNetCore": "Warning"
      }
  }
}

Read appsettings.json File Using File I/O

Because the appsettings.json file only contains text, you can read in the JSON contained in this file using the .NET File I/O classes, as shown in Listing 7. In this code, you set the fileName variable to point to the location of the appsettings.json file. Because the appsettings.json file is in the same folder as the executable that's running, you don't need to specify a path to the file. If the file exists, read all of the text using the ReadAllText() method of the File class and pass that text to the Parse() method of the JsonNode class. Once you have the JSON in the JsonNode object, you can read the value from the ConnectionStrings.DefaultConnection property. Type the code shown in Listing 7 into the Program.cs file, run the application, and you should see the connection string in the appsettings.json file displayed in the console window.

Listing 7: Read the appsettings.json file using .NET File I/O classes

using System.Text.Json.Nodes;

string connectString = string.Empty;
string fileName = "appsettings.json";

if (File.Exists(fileName)) {
  // Read settings from file
  JsonNode? jd = JsonNode.Parse(File.ReadAllText(fileName));

  // Extract the default connection string
  connectString = jd!["ConnectionStrings"]
    !["DefaultConnection"]?.GetValue<string>() ?? string.Empty;
}

Console.WriteLine(connectString);

Writing to a File

If you're running a WPF application, or a console application, it's perfectly acceptable to write data back to the appsettings.json file. Of course, you wouldn't want to do this when running an ASP.NET web application. The code shown in Listing 8 reads in the appsettings.json file, adds a new name/value pair, then writes the new JSON back to the appsettings.json file. Type the code in Listing 8 into the Program.cs file and run the application to produce the following results:

Listing 8: Write a new value to the appsettings.json file

string fileName = "appsettings.json";
JsonObject? jo = null;

if (File.Exists(fileName)) {
  // Read settings from file
  jo = JsonNode.Parse(File.ReadAllText(fileName))?.AsObject();

  if (jo != null) {
    // Locate node to add to
    JsonObject? node = jo!["AdvWorksAppSettings"]?.AsObject();

    // Add new node
    node?.Add("LastDateUsed",
       JsonValue.Create(DateTime.Now.ToShortDateString()));

    // Write back to file
    File.WriteAllText(fileName, jo?.ToString());
  }
}

Console.WriteLine(jo?.ToString());
{
    "ConnectionStrings": {"DefaultConnection": "Server=...;"
    },
    "AdvWorksAppSettings": {
        "ApplicationName": "Adventure Works",
        "LastDateUsed": "12/27/2023"
    },
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    }
}

Using the IConfiguration Interface

Instead of using the .NET File I/O classes, you can take advantage of the IConfiguration interface and the ConfigurationBuilder class to read in a JSON file. To use the ConfigurationBuilder class, you must add two packages to your project.

  • Microsoft.Extensions.Configuration
  • Microsoft.Extensions.Configuration.Json

After adding these two packages to your project, you can write the code shown in Listing 9. In this code, you pass in the runtime configuration file name (see Listing 4) to the AddJsonFile() method on the ConfigurationBuilder. The Build() method is called to create the configuration builder object, which reads the JSON file into memory and converts the text into a JSON document. Use the GetSection() method to retrieve a specific section within the JSON file. In this case, you're asking for the runtimeOptions section. From the section variable, you can now retrieve the framework version number. Type in the code in Listing 9 into the Program.cs file, run the application, and you should see the version number appear in the console window.

Listing 9: Use the ConfigurationBuilder class to read in a JSON file

using Microsoft.Extensions.Configuration;

string? value = string.Empty;
string fileName = $"{AppDomain.CurrentDomain.FriendlyName}
     .runtimeconfig.json";

IConfiguration config = new ConfigurationBuilder()
     .AddJsonFile(fileName)
     .Build();

IConfigurationSection section = config.GetSection("runtimeOptions");
value = section["framework:version"] ?? string.Empty;

Console.WriteLine(value);

Read the appsettings.json File

You previously read the appsettings.json file using the .NET File I/O classes. In Listing 10 you're now going to use the ConfigurationBuilder to read the same file. Because the appsettings.json file is in the same folder as the executable that's running, you don't need to specify a path to the file. Type the code in Listing 10 into the Program.cs file, run the application and you should see the connection string appear in the console window.

Listing 10: Read the appsettings.json file using the ConfigurationBuilder class

using Microsoft.Extensions.Configuration;

string connectString;

IConfiguration config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

IConfigurationSection section = config.GetSection("ConnectionStrings");
connectString = section["DefaultConnection"] ?? string.Empty;

Console.WriteLine(connectString);

Bind Settings to a Class

Instead of reading values one at a time from a configuration file, you can bind a section within a configuration file to a class with just one line of code. Create a class named AppSettings and add a property that maps to each name in the configuration file. In the following code, there's a sole property named ApplicationName that maps to the ApplicationName property in the appsettings.json file shown in Listing 6.

namespace JsonSamples;
public class AppSettings
{
    public string ApplicationName { get; set; }
        = string.Empty;
}

To perform the binding operation, add the package Microsoft.Extensions.Configuration.Binder to your project using the NuGet Package Manager. Add the following code to the Program.cs file and run the application to see the application name displayed in the console window:

using JsonSamples;
using Microsoft.Extensions.Configuration;

AppSettings entity = new();

IConfiguration config = new ConfigurationBuilder()
  .AddJsonFile("appsettings.json")
  .Build();

config.Bind("AdvWorksAppSettings", entity);
Console.WriteLine(entity.ApplicationName);

Serialize C# Object to JSON

So far, everything you've done manipulates JSON using C# code. Another excellent feature of .NET is that you may serialize your C# objects into JSON using just a few lines of code. This is very handy for sending C# objects over the internet via Web API calls. In fact, the code you're going to learn about now is exactly how ASP.NET sends your data across the internet when writing Web API calls. To illustrate this concept, right mouse-click on the project and add a new class named Person, as shown in the following code snippet:

namespace JsonSamples;
public class Person
{
    public string? Name { get; set; }
    public int Age { get; set; }
    public string? SSN { get; set; }
    public bool IsActive { get; set; }
    public override string ToString()
    {
        return $"{Name}, Age={Age},
        SSN={SSN}, IsActive={IsActive}";
    }
}

After creating the Person class, replace all the code in the Program.cs file with the following:

using JsonSamples;
using System.Text.Json;

Person entity = new() {
    Name = "John Smith",
    Age = 31,
    SSN = null,
    IsActive = true
};

Console.WriteLine(JsonSerializer.Serialize(entity));

This code uses the Serialize() method of the JsonSerializer class from the System.Text.Json namespace. Pass in the instance of your C# object to the Serialize() method and a string of JSON is returned. Run the application and you should see the following string of JSON appear in your console window:

{"Name":"John Smith","Age":31,"SSN":null,"IsActive":true}

Notice that there's no indentation or spaces between the values. Also notice that the names are the exact same case as your C# class property names. JSON usually uses camel case for names (first letter is lower-case), whereas C# uses Pascal case (first letter is upper-case).

Change Casing of Property Names

If you wish to change the casing of the property names, create an instance of a JsonSerializerOptions object and set the PropertyNamingPolicy to the enumeration value of CamelCase (as seen in Listing 11). Change the formatting of the JSON to become indented by setting the WriteIndented property to true.

Listing 11: Add a JsonSerializerOptions object to control how the JSON is formatted

using JsonSamples;
using System.Text.Json;

Person entity = new() {
    Name = "John Smith",
    Age = 31,
    SSN = null,
    IsActive = true
};

JsonSerializerOptions options = new() {
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
};

Console.WriteLine(JsonSerializer.Serialize(entity, options));

Type the code in Listing 11 into the Program.cs file and run the application to display the following JSON in the console window:

{
    "name": "John Smith",
    "age": 31,
    "ssn": null,
    "isActive": true
}

Go back to the Program.cs file and change the PropertyNamingPolicy property to JsonNamingPolicy.SnakeCaseUpper to produce each property as upper-case with each word in the property name separated by an underscore, as shown in the following output:

{
    "NAME": "John Smith",
    "AGE": 31,
    "SSN": null,
    "IS_ACTIVE": true
}

Go back to the Program.cs file and change the PropertyNamingPolicy property to JsonNamingPolicy.SnakeCaseLower to produce each property as lower-case with each word in the property name separated by an underscore, as shown in the following output:

{
    "name": "John Smith",
    "age": 31,
    "ssn": null,
    "is_active": true
}

The other enumeration values you may set the PropertyNamingPolicy to are JsonNamingPolicy.KebabCaseUpper or JsonNamingPolicy.KebabCaseLower. This policy separates each word in the property name with a dash instead of an underscore, as shown below:

{
    "name": "John Smith",
    "age": 31,
    "ssn": null,
    "is-active": true
}

Control Serialization Using JSON Attributes

In the System.Text.Json.Serialization namespace are some attributes you may use to decorate C# class properties to help you control how each property is serialized. There are several attributes you may use, but the most used are JsonPropertyName, JsonIgnore, and JsonPropertyOrder. Listing 12 shows a PersonWithAttributes class with these attributes applied to different properties.

Listing 12: Add JSON attributes to your C# class properties to control serialization

using System.Text.Json.Serialization;

namespace JsonSamples;

public class PersonWithAttributes
{
    [JsonPropertyName("personName")]
    [JsonPropertyOrder(1)]
    public string? Name { get; set; }
    [JsonPropertyName("personAge")]
    [JsonPropertyOrder(2)]
    public int Age { get; set; }
    public string? SSN { get; set; }
    public bool IsActive { get; set; }

    [JsonIgnore]
    public DateTime? CreateDate { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public DateTime? ModifiedDate { get; set; }

    public override string ToString()
    {
        return $"{Name}, 
        Age={Age},
        SSN={SSN}, 
        IsActive={IsActive}";
    }
}

When you decorate a C# property with the JsonPropertyName attribute, you pass the JSON name to use to the attribute when this property is serialized. When a C# class is serialized, the order of the properties is in the order they appear in the class. To change the order in which the properties are serialized, add a JsonPropertyOrder attribute to each property and set the order in which you want them to appear. If the JsonPropertyOrder attribute is not applied to a property, the default number is zero (0). To never have a property serialized into JSON, apply the JsonIgnore attribute with no parameters. You may also set the Condition property of the JsonIgnore attribute to not serialize the data when the value is null.

After creating the PersonWithAttributes class, write the code shown in Listing 13 in the Program.cs file. In this listing, notice that the CreateDate and ModifiedDate properties are both set to the current date and time.

Listing 13: Serialize the C# object with the JSON attributes applied

using JsonSamples;
using System.Text.Json;

PersonWithAttributes entity = new() {
  Name = "John Smith",
  Age = 31,
  SSN = null,
  IsActive = true,
  // This property is never serialized
  CreateDate = DateTime.Now,
  // Comment this property to
  // remove from serialization
  ModifiedDate = DateTime.Now
};

JsonSerializerOptions options = new() {
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
};

Console.WriteLine(JsonSerializer.Serialize(entity, options));

If you run the code shown in Listing 13, the following output is displayed in the console window:

{
    "ssn": null,
    "isActive": true,
    "modifiedDate": "2024-01-28T12:48:53",
    "personName": "John Smith",
    "personAge": 31
}

There are a few things to notice about this output. The modifiedDate appears in the output because the value is not null. The Name and Age C# properties are emitted as personName and personAge in JSON. These two properties also appear at the end of the object because their order was set to one (1) and two (2) respectively. If you go back to the code in the Program.cs file, comment the ModifiedDate property, and rerun the application, the ModifiedDate value will not display in the output.

Serializing Objects with Enumerations

Another common scenario is that you have a class with an enumeration as one of the property types. When you serialize that object, you can either emit the numeric value of the enumeration or use the string representation of the enumeration itself. Create a new enumeration named PersonTypeEnum.

namespace JsonSamples;

public enum PersonTypeEnum
{
    Employee = 1,
    Customer = 2,
    Supervisor = 3
}

Create a class named PersonWithEnum that has a PersonType property that is of the type PersonTypeEnum.

namespace JsonSamples;

public class PersonWithEnum
{
    public string? Name { get; set; }
    public PersonTypeEnum PersonType { get; set; }
    public override string ToString()
    {
        return $"{Name}, Type={PersonType}";
    }
}

Open the Program.cs file and write the code shown in the code snippet below:

using JsonSamples;
using System.Text.Json;
PersonWithEnum entity = new() {
    Name = "John Smith",
    PersonType = PersonTypeEnum.Supervisor
};

JsonSerializerOptions options = new() {
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
};

Console.WriteLine(JsonSerializer.Serialize(entity, options));

When you run the application, the following output is displayed in the console window.

{
    "name": "John Smith",
    "personType": 3
}

Notice that the personType property has a value of 3, which equates to the Supervisor enumeration value. Go back to the Program.cs file and add the following using statement at the top of the file:

using System.Text.Json.Serialization;

Set the Converters property in the JsonSerializerOptions object to use an instance of the JsonStringEnumConverter class. This class works with the serializer and instead of emitting the numeric value of enumeration properties, it emits the string representation of the enumeration.

JsonSerializerOptions options = new() {
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true,
    Converters =
    {
        new JsonStringEnumConverter()
    }
};

After adding the JsonStringEnumConverter object, run the application and the following should now display in the console window:

{
  "name": "John Smith",
  "personType": "Supervisor"
}

Serialize a Nested Object

Often you have a property in a class that is itself another class. Don't worry, the JSON serialization process handles this situation just fine. To illustrate, create a class called JwtSettings, as shown in Listing 14.

Listing 14: Create a JwtSettings class to be nested within an AppSettings class

namespace JsonSamples;

public class JwtSettings
{
    public string Key { get; set; } = string.Empty; 
    public string Issuer { get; set; } = string.Empty;
    public string Audience { get; set; } = string.Empty;
    public int MinutesToExpiration { get; set; }
    public string[] AllowedIPAddresses { get; set; } = [];

    #region ToString Override
    public override string ToString()
    {
        return $"{Key} - {Issuer} – {Audience} - {MinutesToExpiration}";
    }
    #endregion
}

Next, create a class named AppSettingsNested that has two properties: ApplicationName and JWTSettings. The data type for the JWTSettings property is the JwtSettings class you just created.

namespace JsonSamples;
public class AppSettingsNested
{
    public string ApplicationName { get; set; } = string.Empty;
    public JwtSettings JWTSettings { get; set; } = new();
}

Now that you have a nested class, write the code shown in Listing 15 into the Program.cs file. In this code, you fill in the ApplicationName property, create a new instance of a JwtSettings class for the JWTSettings property, and then fill in each property in that class too.

Listing 15: Write code to serialize a nested object to view the JSON output

using JsonSamples;
using System.Text.Json;

AppSettingsNested entity = new() {
    ApplicationName = "JSON Samples",
    JWTSettings = new() {
        Key = "ALongKeyForASymmetricAlgorithm",
        Issuer = "JsonSamplesAPI",
        Audience = "PDSCJsonSamples",
        MinutesToExpiration = 60
    }
};

JsonSerializerOptions options = new() {
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
};

Console.WriteLine(JsonSerializer.Serialize(entity, options));

Run the application and you should see the following displayed in your console window:

{
  "applicationName": "JSON Samples",
  "jwtSettings": {
      "key": "ALongKeyForASymmetricAlgorithm",
      "issuer": "JsonSamplesAPI",
      "audience": "PDSCJsonSamples",
      "minutesToExpiration": 60,
      "allowedIPAddresses": []
    }
}

Serialize a Dictionary

If you have a generic Dictionary<TKey, TValue> object, or a KeyValuePair object filled with data, the JSON serializer emits those as a single object. Open the Program.cs file and add the code shown below.

using System.Text.Json;
Dictionary<string, object?> dict = new()
    {
        {"name", "John Smith"},
        {"age", 31},
        {"isActive", true}
    };

JsonSerializerOptions options = new() {
    WriteIndented = true
};

    Console.WriteLine(JsonSerializer.Serialize(dict, options));

When you run the application, the console window displays the following JSON object:

{
    "name": "John Smith",
    "age": 31,
    "isActive": true
}

Serialize a List

To write a JSON array, you can use any of the IEnumerable objects in .NET such as an array or a List<T>. To illustrate, use the PersonWithEnum class and create a generic list of two PersonWithEnum objects, as shown in Listing 16.

Listing 16: Create a generic List and serialize it to create a JSON array

using JsonSamples;
using System.Text.Json;

List<PersonWithEnum> list = new()
{
    new PersonWithEnum {
        Name = "John Smith",
        PersonType = PersonTypeEnum.Supervisor
    },
    new PersonWithEnum { 
        Name = "Sally Jones",
        PersonType = PersonTypeEnum.Employee
    }
};

JsonSerializerOptions options = new() {
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
};

Console.WriteLine(JsonSerializer.Serialize(list, options));

Type the code shown in Listing 16 into the Program.cs file and run the application to display the following output in the console window.

[
    {
        "name": "John Smith",
        "personType": 3
    },
    {
        "name": "Sally Jones",
        "personType": 1
    }
]

Deserialize JSON into a C# Object

Now that you've seen how to serialize a C# object into JSON, let's look at reversing the process. To illustrate, create a JSON string with the JSON property name that exactly matches the C# property name in the class you wish to deserialize this JSON into. You use the JsonSerializer class (Listing 17) like you did for serializing, but call the Deserialize() method passing in the data type you wish to deserialize the JSON string into and the string itself.

Listing 17: Use the Deserialize() method to convert a JSON string into a C# object

using JsonSamples;
using System.Text.Json;

// JSON names must match
// C# properties by default
string json = @"{
    ""Name"": ""John Smith"",
    ""Age"": 31,
    ""SSN"": null,
    ""IsActive"": true
}";

// Deserialize JSON string into Person
Person? entity = JsonSerializer.Deserialize<Person>(json);

Console.WriteLine(entity);

Type the code in Listing 17 into the Program.cs file and run the application to see the following results displayed in the console window:

John Smith, Age=31, SSN=, IsActive=True

Use Serialization Options

Just like you did when serializing, if the case of the JSON property names doesn't match the C# property names, you may set the PropertyNameCaseInsensitive property to true in the options and pass those options to the Deserialize() method, as shown in Listing 18. Notice that in this listing the JSON property names start with lower-case. If you forget to use the options and the property names have different casing, then no data is mapped from the JSON to the C# object, so an empty object is returned from the Deserialize() method.

Listing 18: Pass in options to control the deserialization process

using JsonSamples;
using System.Text.Json;

string json = @"{
    ""name"": ""John Smith"",
    ""age"": 31,
    ""ssn"": null,
    ""isActive"": true
}";

// Override case matching
JsonSerializerOptions options = new() {
    PropertyNameCaseInsensitive = true
};

// Deserialize JSON string into Person
Person? entity = JsonSerializer.Deserialize<Person>(json, options);

Console.WriteLine(entity);

Deserialize Using the JsonNode Object

Another option for deserializing JSON into a C# object is to use either the JsonNode or the JsonDocument classes. The code shown in Listing 19 uses the JsonNode object to illustrate. The JsonDocument class looks very similar to that of the JsonNode. Type this code into the Program.cs file and run the application to get the same output as you saw in the last example.

Listing 19: Use the JsonNode object to deserialize a JSON string into a C# object

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

string json = @"{
    ""name"": ""John Smith"",
    ""age"": 31,
    ""ssn"": null,
    ""isActive"": true
}";

JsonSerializerOptions options = new() {
    PropertyNameCaseInsensitive = true,
};

// Parse string into a JsonNode object
JsonNode? jn = JsonNode.Parse(json);

// Deserialize JSON string into Person
// using the JsonNode object
Person? entity = jn.Deserialize<Person>(options);

Console.WriteLine(entity);

Deserializing Enumeration Values

If you know that the JSON object is going to have the string representation of a C# enumeration, set the Converters property to a new instance of the JsonStringEnumConverter class in the JsonSerializerOptions object, as shown in Listing 20. If you forget to include the Converters property on the JsonSerializerOptions, a JsonException is thrown.

Listing 20: If you use enumerations, be sure to include the JsonStringEnumConverter in the serialization options

using JsonSamples;
using System.Text.Json.Serialization;
using System.Text.Json;

string json = @"{
    ""name"": ""John Smith"",
    ""personType"": ""Supervisor""
}";

JsonSerializerOptions options = new() {
    PropertyNameCaseInsensitive = true,
    // Avoid an exception being thrown on Deserialize()
    Converters =
    {
        new JsonStringEnumConverter()
    }
};

// Deserialize JSON string
PersonWithEnum? entity = JsonSerializer
  .Deserialize<PersonWithEnum>(json, options);

Console.WriteLine(entity);

Type the code shown in Listing 20 into the Program.cs file and run the application to see the following output appear in the console window:

John Smith, Type=Supervisor

Convert JSON File to a Person Object

Right mouse-click on the JsonSamples console application and create a new folder named JsonSampleFiles. Right mouse-click on the JsonSampleFiles folder and add a new file named person.json. Place the following JSON object into this file:

{
    "name": "Sally Jones",
    "age": 39,
    "ssn": "555-55-5555",
    "isActive": true
}

Open the Program.cs file and replace the contents of the file with the code shown in Listing 21. In this sample, you're using a FileStream object to open the file and stream it to the Deserialize() method. Thus, you should use the Using statement in front of the FileStream declaration so it will be disposed of properly.

Listing 21: Read a JSON file and convert the JSON object in the file into a C# object

using JsonSamples;
using System.Text.Json;

string fileName = $"{AppDomain.CurrentDomain.BaseDirectory}
  JsonSampleFiles\\person.json";

using FileStream stream = File.OpenRead(fileName);

JsonSerializerOptions options = new() {
    PropertyNameCaseInsensitive = true,
};

// Deserialize JSON string into Person
Person? entity = JsonSerializer.Deserialize<Person>(stream, options);

Console.WriteLine(entity);

Run the application and you should see the following output displayed in the console window:

Sally Jones, Age=39, SSN=555-55-5555, IsActive=True

Convert a JSON Array in a File to a List of Person Objects

Right mouse-click on the JsonSampleFiles folder and add a new file named persons.json. Place the following JSON array into this file:

[
    {
        "name": "John Smith",
        "age": 31,
        "ssn": null,
        "isActive": true
    },
    {
        "name": "Sally Jones",
        "age": 39,
        "ssn": "555-55-5555",
        "isActive": true
    }
]

Open the Program.cs file and type in the code shown in Listing 22. This code is almost the same as the code you wrote to deserialize a single person object; the only difference is that you pass the data type List<Person> to the Deserialize() method. Once you have the collection of Person objects, iterate over the collection and display each person on the console window.

Listing 22: Read an array of JSON objects from a file and convert to a list of person objects

using JsonSamples;
using System.Text.Json;

string fileName = $"{AppDomain.CurrentDomain.BaseDirectory}
  JsonSampleFiles\\persons.json";

using FileStream stream = File.OpenRead(fileName);

JsonSerializerOptions options = new() {
    PropertyNameCaseInsensitive = true,
};

// Deserialize JSON string into List<Person>
List<Person>? list = JsonSerializer
  .Deserialize<List<Person>>(stream, options);

if (list != null) {
    foreach (var item in list) {
        Console.WriteLine(item);
    }
}

Run the application and you should see the following output displayed in the console window:

John Smith, Age=31, SSN=, IsActive=True
Sally Jones, Age=39, SSN=555-55-5555, IsActive=True

Get Maximum Age from List of Person Objects

After reading in a list of objects, you may now use LINQ operations or any Enumerable methods such as Min, Max, Sum, and Average on that list. In the code shown in Listing 23, the Max() method is applied to the list and the maximum value found in the Age property is displayed on the console window. When you run this application, the value reported back should be thirty-nine (39).

Listing 23: After deserializing a list of person objects, apply the Max() method to calculate the largest numeric value

using JsonSamples;
using System.Text.Json;

string fileName = $"{AppDomain.CurrentDomain.BaseDirectory}
 JsonSampleFiles\\persons.json";

using FileStream stream = File.OpenRead(fileName);

JsonSerializerOptions options = new() {
  PropertyNameCaseInsensitive = true,
};

// Deserialize JSON string
List<Person>? list = JsonSerializer
  .Deserialize<List<Person>>(stream, options);

// Calculate maximum age
int maxAge = list?.Max(row => row.Age) ?? 0;

Console.WriteLine(maxAge);

Using the Utf8JsonWriter Class

The Utf8JsonWriter class is a high-performance, forward-only, non-cached method of writing JSON documents. Just like with serialization, you can control the output of the JSON to include white space, and indentation. Listing 24 shows how to write a single JSON object into a MemoryStream object. Note that both the MemoryStream and the Utf8JsonWriter objects implement the IDisposable interface, so you need to prefix them with the Using statement. You must start each JSON document by calling the WriteStartObject() or the WriteStartArray() method. You then call the appropriate method to write a string, a number, a Boolean, a null, or a comment. Finally, call the WriteEndObject() or the WriteEndArray() method to close the JSON document.

Listing 24: The Utf8JsonWriter object is a forward-only cursor for emitting JSON quickly

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

JsonWriterOptions options = new() {
  Indented = true
};

using MemoryStream ms = new();
using Utf8JsonWriter writer = new(ms, options);

writer.WriteStartObject();
writer.WriteString("name", "John Smith");
writer.WriteNumber("age", 31);
writer.WriteBoolean("isActive", true);
writer.WriteEndObject();
writer.Flush();

string json = Encoding.UTF8.GetString(ms.ToArray());

Console.WriteLine(json);

Type the code shown in Listing 24 into the Program.cs file and run the application to display the output shown below in the console window:

{
    "name": "John Smith",
    "age": 31,
    "isActive": true
}

Write a JSON Array

As just mentioned, you may also write a JSON array of data using the Utf8JsonWriter class. The only difference is that you start writing using the WriteStartArray() method, and then call the WriteStartObject() method to create your first JSON object in the array. You then continue this process until all your array elements are written. Finish the JSON array document by calling the WriteEndArray() method, as shown in Listing 25.

Listing 25: The Utf8JsonWriter object can write arrays as well as single objects

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

JsonWriterOptions options = new() {
  Indented = true
};

using MemoryStream ms = new();
using Utf8JsonWriter writer = new(ms, options);

writer.WriteStartArray();
writer.WriteStartObject();
writer.WriteString("name", "John Smith");
writer.WriteNumber("age", 31);
writer.WriteBoolean("isActive", true);
writer.WriteEndObject();
writer.WriteStartObject();
writer.WriteString("name", "Sally Jones");
writer.WriteNumber("age", 39);
writer.WriteBoolean("isActive", true);
writer.WriteEndObject();
writer.WriteEndArray();
writer.Flush();

string json = Encoding.UTF8.GetString(ms.ToArray());

Console.WriteLine(json);

Type the code shown in Listing 25 into the Program.cs file and run the application to display the output shown below in the console window:

[
    {
        "name": "John Smith",
        "age": 31,
        "isActive": true
    },
    {
        "name": "Sally Jones",
        "age": 39,
        "isActive": true
    }
]

Summary

In this article, you were introduced to the many different classes in .NET that are used to manipulate JSON documents. If you need fast, read-only access to JSON documents, the JsonDocument class is what you should use. If you need the ability to modify JSON in your application, use the JsonNode class. Serialization is accomplished using the JsonSerialization, JsonDocument, or the JsonNode classes. When you're dealing with configuration files such as the appsettings.json file in your application, take advantage of the IConfiguration interface and the ConfigurationBuilder class. Finally, to write JSON documents one name/value pair at a time, the Utf8JsonWriter class gives you the most control over how your document is formatted.

Table 1: JSON has a limited set of data types available for a value.

Data TypeDescription
NumberA signed number value. The number may be a whole number (integer) or a decimal with a fractional part.
StringA set of zero or more Unicode characters enclosed within double quotes. Strings support a backslash escaping syntax just as you find in C#.
BooleanEither a true or a false value.
ObjectA JSON object using the JSON object syntax previously explained.
ArrayA set of zero or more values using the JSON array syntax previously explained. Each element within the array may be of any of the types shown in this table.
NullUse the keyword null to signify an empty value for this name.

Table 2: The System.Text.Json namespace contains classes and structures for manipulating and serializing JSON objects

Class / StructureDescription
JsonDocumentA class that represents an immutable (read-only) document object model (DOM) of a JSON object. Use this class when you don't have a C# class to deserialize the JSON into and you need to access the name/value pairs programmatically.
JsonPropertyThis structure represents a single JSON property within a JSON object. For example, "colorId": 1 is an example of a JsonProperty.
JsonElementThis structure represents a single value within a JSON property. For example, the number one (1) within the property "colorId": 1 is the JsonElement.
JsonSerializerA class used to serialize a JSON string into a C# object, or to deserialize a C# object into a JSON string.
Utf8JsonWriterA class that can be used to emit a JSON document one property at a time. This class is a high-performance, forward-only, non-cached method of writing UTF-8 encoded JSON text.

Table 3: The System.Text.Json.Nodes namespace contains classes for manipulating in-memory JSON objects as a document object model

ClassDescription
JsonObjectThis class represents a mutable (read/write) JSON document. This class is like the JsonDocument class from the System.Text.Json namespace.
JsonNodeThis class represents a mutable (read/write) node within a JSON document. This class is like the JsonProperty class from the System.Text.Json namespace.
JsonValueThis class represents a mutable (read/write) JSON value. This class is like the JsonElement class from the System.Text.Json namespace.
JsonArrayThis class represents a mutable (read/write) JSON array.