Hướng dẫn dùng jsondocument JavaScript

Skip to main content

This browser is no longer supported.

Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.

How to use a JSON document, Utf8JsonReader, and Utf8JsonWriter in System.Text.Json

  • Article
  • 04/02/2022
  • 26 minutes to read

In this article

This article shows how to use:

  • A JSON Document Object Model (DOM) for random access to data in a JSON payload.
  • The Utf8JsonWriter type for building custom serializers.
  • The Utf8JsonReader type for building custom parsers and deserializers.

JSON DOM choices

Working with a DOM is an alternative to deserialization with JsonSerializer:

  • When you don't have a type to deserialize into.
  • When the JSON you receive doesn't have a fixed schema and must be inspected to know what it contains.

System.Text.Json provides two ways to build a JSON DOM:

  • JsonDocument provides the ability to build a read-only DOM by using Utf8JsonReader. The JSON elements that compose the payload can be accessed via the JsonElement type. The JsonElement type provides array and object enumerators along with APIs to convert JSON text to common .NET types. JsonDocument exposes a RootElement property. For more information, see Use JsonDocument later in this article.

  • JsonNode and the classes that derive from it in the System.Text.Json.Nodes namespace provide the ability to create a mutable DOM. The JSON elements that compose the payload can be accessed via the JsonNode, JsonObject, JsonArray, JsonValue, and JsonElement types. For more information, see Use JsonNode later in this article.

Consider the following factors when choosing between JsonDocument and JsonNode:

  • The JsonNode DOM can be changed after it's created. The JsonDocument DOM is immutable.
  • The JsonDocument DOM provides faster access to its data.

  • Starting in .NET 6, JsonNode and the classes that derive from it in the System.Text.Json.Nodes namespace provide the ability to create a mutable DOM. For more information, see the .NET 6 version of this article.

Use JsonNode

The following example shows how to use JsonNode and the other types in the System.Text.Json.Nodes namespace to:

  • Create a DOM from a JSON string
  • Write JSON from a DOM.
  • Get a value, object, or array from a DOM.
using System.Text.Json;
using System.Text.Json.Nodes;

namespace JsonNodeFromStringExample;

public class Program
{
    public static void Main()
    {
        string jsonString =
@"{
  ""Date"": ""2019-08-01T00:00:00"",
  ""Temperature"": 25,
  ""Summary"": ""Hot"",
  ""DatesAvailable"": [
    ""2019-08-01T00:00:00"",
    ""2019-08-02T00:00:00""
  ],
  ""TemperatureRanges"": {
      ""Cold"": {
          ""High"": 20,
          ""Low"": -10
      },
      ""Hot"": {
          ""High"": 60,
          ""Low"": 20
      }
  }
}
";
        // Create a JsonNode DOM from a JSON string.
        JsonNode forecastNode = JsonNode.Parse(jsonString)!;

        // Write JSON from a JsonNode
        var options = new JsonSerializerOptions { WriteIndented = true };
        Console.WriteLine(forecastNode!.ToJsonString(options));
        // output:
        //{
        //  "Date": "2019-08-01T00:00:00",
        //  "Temperature": 25,
        //  "Summary": "Hot",
        //  "DatesAvailable": [
        //    "2019-08-01T00:00:00",
        //    "2019-08-02T00:00:00"
        //  ],
        //  "TemperatureRanges": {
        //    "Cold": {
        //      "High": 20,
        //      "Low": -10
        //    },
        //    "Hot": {
        //      "High": 60,
        //      "Low": 20
        //    }
        //  }
        //}

        // Get value from a JsonNode.
        JsonNode temperatureNode = forecastNode!["Temperature"]!;
        Console.WriteLine($"Type={temperatureNode.GetType()}");
        Console.WriteLine($"JSON={temperatureNode.ToJsonString()}");
        //output:
        //Type = System.Text.Json.Nodes.JsonValue`1[System.Text.Json.JsonElement]
        //JSON = 25

        // Get a typed value from a JsonNode.
        int temperatureInt = (int)forecastNode!["Temperature"]!;
        Console.WriteLine($"Value={temperatureInt}");
        //output:
        //Value=25

        // Get a typed value from a JsonNode by using GetValue.
        temperatureInt = forecastNode!["Temperature"]!.GetValue();
        Console.WriteLine($"TemperatureInt={temperatureInt}");
        //output:
        //Value=25

        // Get a JSON object from a JsonNode.
        JsonNode temperatureRanges = forecastNode!["TemperatureRanges"]!;
        Console.WriteLine($"Type={temperatureRanges.GetType()}");
        Console.WriteLine($"JSON={temperatureRanges.ToJsonString()}");
        //output:
        //Type = System.Text.Json.Nodes.JsonObject
        //JSON = { "Cold":{ "High":20,"Low":-10},"Hot":{ "High":60,"Low":20} }

        // Get a JSON array from a JsonNode.
        JsonNode datesAvailable = forecastNode!["DatesAvailable"]!;
        Console.WriteLine($"Type={datesAvailable.GetType()}");
        Console.WriteLine($"JSON={datesAvailable.ToJsonString()}");
        //output:
        //datesAvailable Type = System.Text.Json.Nodes.JsonArray
        //datesAvailable JSON =["2019-08-01T00:00:00", "2019-08-02T00:00:00"]

        // Get an array element value from a JsonArray.
        JsonNode firstDateAvailable = datesAvailable[0]!;
        Console.WriteLine($"Type={firstDateAvailable.GetType()}");
        Console.WriteLine($"JSON={firstDateAvailable.ToJsonString()}");
        //output:
        //Type = System.Text.Json.Nodes.JsonValue`1[System.Text.Json.JsonElement]
        //JSON = "2019-08-01T00:00:00"

        // Get a typed value by chaining references.
        int coldHighTemperature = (int)forecastNode["TemperatureRanges"]!["Cold"]!["High"]!;
        Console.WriteLine($"TemperatureRanges.Cold.High={coldHighTemperature}");
        //output:
        //TemperatureRanges.Cold.High = 20

        // Parse a JSON array
        var datesNode = JsonNode.Parse(@"[""2019-08-01T00:00:00"",""2019-08-02T00:00:00""]");
        JsonNode firstDate = datesNode![0]!.GetValue();
        Console.WriteLine($"firstDate={ firstDate}");
        //output:
        //firstDate = "2019-08-01T00:00:00"
    }
}

Create a JsonNode DOM with object initializers and make changes

The following example shows how to:

  • Create a DOM by using object initializers.
  • Make changes to a DOM.
using System.Text.Json;
using System.Text.Json.Nodes;

namespace JsonNodeFromObjectExample;

public class Program
{
    public static void Main()
    {
        // Create a new JsonObject using object initializers.
        var forecastObject = new JsonObject
        {
            ["Date"] = new DateTime(2019, 8, 1),
            ["Temperature"] = 25,
            ["Summary"] = "Hot",
            ["DatesAvailable"] = new JsonArray(
                new DateTime(2019, 8, 1), new DateTime(2019, 8, 2)),
            ["TemperatureRanges"] = new JsonObject
            {
                ["Cold"] = new JsonObject
                {
                    ["High"] = 20,
                    ["Low"] = -10
                }
            },
            ["SummaryWords"] = new JsonArray("Cool", "Windy", "Humid")
        };

        // Add an object.
        forecastObject!["TemperatureRanges"]!["Hot"] =
            new JsonObject { ["High"] = 60, ["Low"] = 20 };

        // Remove a property.
        forecastObject.Remove("SummaryWords");

        // Change the value of a property.
        forecastObject["Date"] = new DateTime(2019, 8, 3);

        var options = new JsonSerializerOptions { WriteIndented = true };
        Console.WriteLine(forecastObject.ToJsonString(options));
        //output:
        //{
        //  "Date": "2019-08-03T00:00:00",
        //  "Temperature": 25,
        //  "Summary": "Hot",
        //  "DatesAvailable": [
        //    "2019-08-01T00:00:00",
        //    "2019-08-02T00:00:00"
        //  ],
        //  "TemperatureRanges": {
        //    "Cold": {
        //      "High": 20,
        //      "Low": -10
        //    },
        //    "Hot": {
        //      "High": 60,
        //      "Low": 20
        //    }
        //  }
        //}
    }
}

Deserialize subsections of a JSON payload

The following example shows how to use JsonNode to navigate to a subsection of a JSON tree and deserialize a single value, a custom type, or an array from that subsection.

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

namespace JsonNodePOCOExample;

public class TemperatureRanges : Dictionary
{
}

public class HighLowTemps
{
    public int High { get; set; }
    public int Low { get; set; }
}

public class Program
{
    public static DateTime[]? DatesAvailable { get; set; }

    public static void Main()
    {
        string jsonString =
@"{
  ""Date"": ""2019-08-01T00:00:00"",
  ""Temperature"": 25,
  ""Summary"": ""Hot"",
  ""DatesAvailable"": [
    ""2019-08-01T00:00:00"",
    ""2019-08-02T00:00:00""
  ],
  ""TemperatureRanges"": {
      ""Cold"": {
          ""High"": 20,
          ""Low"": -10
      },
      ""Hot"": {
          ""High"": 60,
          ""Low"": 20
      }
  }
}
";
        // Parse all of the JSON.
        JsonNode forecastNode = JsonNode.Parse(jsonString)!;

        // Get a single value
        int hotHigh = forecastNode["TemperatureRanges"]!["Hot"]!["High"]!.GetValue();
        Console.WriteLine($"Hot.High={hotHigh}");
        // output:
        //Hot.High=60

        // Get a subsection and deserialize it into a custom type.
        JsonObject temperatureRangesObject = forecastNode!["TemperatureRanges"]!.AsObject();
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream);
        temperatureRangesObject.WriteTo(writer);
        writer.Flush();
        TemperatureRanges? temperatureRanges = 
            JsonSerializer.Deserialize(stream.ToArray());
        Console.WriteLine($"Cold.Low={temperatureRanges!["Cold"].Low}, Hot.High={temperatureRanges["Hot"].High}");
        // output:
        //Cold.Low=-10, Hot.High=60

        // Get a subsection and deserialize it into an array.
        JsonArray datesAvailable = forecastNode!["DatesAvailable"]!.AsArray()!;
        Console.WriteLine($"DatesAvailable[0]={datesAvailable[0]}");
        // output:
        //DatesAvailable[0]=8/1/2019 12:00:00 AM
    }
}

JsonNode average grade example

The following example selects a JSON array that has integer values and calculates an average value:

using System.Text.Json.Nodes;

namespace JsonNodeAverageGradeExample;

public class Program
{
    public static void Main()
    {
        string jsonString =
@"{
  ""Class Name"": ""Science"",
  ""Teacher\u0027s Name"": ""Jane"",
  ""Semester"": ""2019-01-01"",
  ""Students"": [
    {
      ""Name"": ""John"",
      ""Grade"": 94.3
    },
    {
      ""Name"": ""James"",
      ""Grade"": 81.0
    },
    {
      ""Name"": ""Julia"",
      ""Grade"": 91.9
    },
    {
      ""Name"": ""Jessica"",
      ""Grade"": 72.4
    },
    {
      ""Name"": ""Johnathan""
    }
  ],
  ""Final"": true
}
";
        double sum = 0;
        int count = 0;

        JsonNode document = JsonNode.Parse(jsonString)!;

        JsonNode root = document.Root;
        JsonArray studentsArray = root["Students"]!.AsArray();

        count = studentsArray.Count;

        foreach (JsonNode? student in studentsArray)
        {
            if (student?["Grade"] is JsonNode gradeNode)
            {
                sum += (double)gradeNode;
            }
            else
            {
                sum += 70;
            }
        }

        double average = sum / count;
        Console.WriteLine($"Average grade : {average}");
    }
}
// output:
//Average grade : 81.92

The preceding code:

  • Calculates an average grade for objects in a Students array that have a Grade property.
  • Assigns a default grade of 70 for students who don't have a grade.
  • Gets the number of students from the Count property of JsonArray.

JsonNode with JsonSerializerOptions

You can use JsonSerializer to serialize and deserialize an instance of JsonNode. However, if you use an overload that takes JsonSerializerOptions, the options instance is only used to get custom converters. Other features of the options instance are not used. For example, if you set JsonSerializerOptions.DefaultIgnoreCondition to WhenWritingNull and call JsonSerializer with an overload that takes JsonSerializerOptions, null properties won't be ignored.

The same limitation applies to the JsonNode methods that take a JsonSerializerOptions parameter: WriteTo(Utf8JsonWriter, JsonSerializerOptions) and ToJsonString(JsonSerializerOptions). These APIs use JsonSerializerOptions only to get custom converters.

The following example illustrates the result of using methods that take a JsonSerializerOptions parameter and serialize a JsonNode instance:

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

namespace JsonNodeWithJsonSerializerOptions;

public class Program
{
    public static void Main()
    {
        Person person = new Person { Name = "Nancy" };

        // Default serialization - Address property included with null token.
        // Output: {"Name":"Nancy","Address":null}
        string personJsonWithNull = JsonSerializer.Serialize(person);
        Console.WriteLine(personJsonWithNull);

        // Serialize and ignore null properties - null Address property is omitted
        // Output: {"Name":"Nancy"}
        JsonSerializerOptions options = new()
        {
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
        };
        string personJsonWithoutNull = JsonSerializer.Serialize(person, options);
        Console.WriteLine(personJsonWithoutNull);

        // Ignore null properties doesn't work when serializing JsonNode instance
        // by using JsonSerializer.
        // Output: {"Name":"Nancy","Address":null}
        var personJsonNode = JsonSerializer.Deserialize(personJsonWithNull);
        personJsonWithNull = JsonSerializer.Serialize(personJsonNode, options);
        Console.WriteLine(personJsonWithNull);

        // Ignore null properties doesn't work when serializing JsonNode instance
        // by using JsonNode.ToJsonString method.
        // Output: {"Name":"Nancy","Address":null}
        personJsonWithNull = personJsonNode!.ToJsonString(options);
        Console.WriteLine(personJsonWithNull);

        // Ignore null properties doesn't work when serializing JsonNode instance
        // by using JsonNode.WriteTo method.
        // Output: {"Name":"Nancy","Address":null}
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream);
        personJsonNode!.WriteTo(writer, options);
        writer.Flush();
        personJsonWithNull = Encoding.UTF8.GetString(stream.ToArray());
        Console.WriteLine(personJsonWithNull);
    }
}
public class Person
{
    public string? Name { get; set; }
    public string? Address { get; set; }
}

If you need features of JsonSerializerOptions other than custom converters, use JsonSerializer with strongly typed targets (such as the Person class in this example) rather than JsonNode.

Use JsonDocument

The following example shows how to use the JsonDocument class for random access to data in a JSON string:

double sum = 0;
int count = 0;

using (JsonDocument document = JsonDocument.Parse(jsonString))
{
    JsonElement root = document.RootElement;
    JsonElement studentsElement = root.GetProperty("Students");
    foreach (JsonElement student in studentsElement.EnumerateArray())
    {
        if (student.TryGetProperty("Grade", out JsonElement gradeElement))
        {
            sum += gradeElement.GetDouble();
        }
        else
        {
            sum += 70;
        }
        count++;
    }
}

double average = sum / count;
Console.WriteLine($"Average grade : {average}");
Dim sum As Double = 0
Dim count As Integer = 0
Using document As JsonDocument = JsonDocument.Parse(jsonString)
    Dim root As JsonElement = document.RootElement
    Dim studentsElement As JsonElement = root.GetProperty("Students")
    For Each student As JsonElement In studentsElement.EnumerateArray()
        Dim gradeElement As JsonElement = Nothing
        If student.TryGetProperty("Grade", gradeElement) Then
            sum += gradeElement.GetDouble()
        Else
            sum += 70
        End If
        count += 1
    Next
End Using

Dim average As Double = sum / count
Console.WriteLine($"Average grade : {average}")

The preceding code:

  • Assumes the JSON to analyze is in a string named jsonString.
  • Calculates an average grade for objects in a Students array that have a Grade property.
  • Assigns a default grade of 70 for students who don't have a grade.
  • Creates the JsonDocument instance in a using statement because JsonDocument implements IDisposable. After a JsonDocument instance is disposed, you lose access to all of its JsonElement instances also. To retain access to a JsonElement instance, make a copy of it before the parent JsonDocument instance is disposed. To make a copy, call JsonElement.Clone. For more information, see JsonDocument is IDisposable.

The preceding example code counts students by incrementing a count variable with each iteration. An alternative is to call GetArrayLength, as shown in the following example:

double sum = 0;
int count = 0;

using (JsonDocument document = JsonDocument.Parse(jsonString))
{
    JsonElement root = document.RootElement;
    JsonElement studentsElement = root.GetProperty("Students");

    count = studentsElement.GetArrayLength();

    foreach (JsonElement student in studentsElement.EnumerateArray())
    {
        if (student.TryGetProperty("Grade", out JsonElement gradeElement))
        {
            sum += gradeElement.GetDouble();
        }
        else
        {
            sum += 70;
        }
    }
}

double average = sum / count;
Console.WriteLine($"Average grade : {average}");
Dim sum As Double = 0
Dim count As Integer = 0
Using document As JsonDocument = JsonDocument.Parse(jsonString)
    Dim root As JsonElement = document.RootElement
    Dim studentsElement As JsonElement = root.GetProperty("Students")

    count = studentsElement.GetArrayLength()

    For Each student As JsonElement In studentsElement.EnumerateArray()
        Dim gradeElement As JsonElement = Nothing
        If student.TryGetProperty("Grade", gradeElement) Then
            sum += gradeElement.GetDouble()
        Else
            sum += 70
        End If
    Next
End Using

Dim average As Double = sum / count
Console.WriteLine($"Average grade : {average}")

Here's an example of the JSON that this code processes:

{
  "Class Name": "Science",
  "Teacher\u0027s Name": "Jane",
  "Semester": "2019-01-01",
  "Students": [
    {
      "Name": "John",
      "Grade": 94.3
    },
    {
      "Name": "James",
      "Grade": 81.0
    },
    {
      "Name": "Julia",
      "Grade": 91.9
    },
    {
      "Name": "Jessica",
      "Grade": 72.4
    },
    {
      "Name": "Johnathan"
    }
  ],
  "Final": true
}

How to search a JsonDocument and JsonElement for sub-elements

Searches on JsonElement require a sequential search of the properties and hence are relatively slow (for example when using TryGetProperty). System.Text.Json is designed to minimize initial parse time rather than lookup time. Therefore, use the following approaches to optimize performance when searching through a JsonDocument object:

  • Use the built-in enumerators (EnumerateArray and EnumerateObject) rather than doing your own indexing or loops.
  • Don't do a sequential search on the whole JsonDocument through every property by using RootElement. Instead, search on nested JSON objects based on the known structure of the JSON data. For example, the preceding code examples look for a Grade property in Student objects by looping through the Student objects and getting the value of Grade for each, rather than searching through all JsonElement objects looking for Grade properties. Doing the latter would result in unnecessary passes over the same data.

Use JsonDocument to write JSON

The following example shows how to write JSON from a JsonDocument:

string jsonString = File.ReadAllText(inputFileName);

var writerOptions = new JsonWriterOptions
{
    Indented = true
};

var documentOptions = new JsonDocumentOptions
{
    CommentHandling = JsonCommentHandling.Skip
};

using FileStream fs = File.Create(outputFileName);
using var writer = new Utf8JsonWriter(fs, options: writerOptions);
using JsonDocument document = JsonDocument.Parse(jsonString, documentOptions);

JsonElement root = document.RootElement;

if (root.ValueKind == JsonValueKind.Object)
{
    writer.WriteStartObject();
}
else
{
    return;
}

foreach (JsonProperty property in root.EnumerateObject())
{
    property.WriteTo(writer);
}

writer.WriteEndObject();

writer.Flush();
Dim jsonString As String = File.ReadAllText(inputFileName)

Dim writerOptions As JsonWriterOptions = New JsonWriterOptions With {
    .Indented = True
}

Dim documentOptions As JsonDocumentOptions = New JsonDocumentOptions With {
    .CommentHandling = JsonCommentHandling.Skip
}

Dim fs As FileStream = File.Create(outputFileName)
Dim writer As Utf8JsonWriter = New Utf8JsonWriter(fs, options:=writerOptions)
Dim document As JsonDocument = JsonDocument.Parse(jsonString, documentOptions)

Dim root As JsonElement = document.RootElement

If root.ValueKind = JsonValueKind.[Object] Then
    writer.WriteStartObject()
Else
    Return
End If

For Each [property] As JsonProperty In root.EnumerateObject()
    [property].WriteTo(writer)
Next

writer.WriteEndObject()

writer.Flush()

The preceding code:

  • Reads a JSON file, loads the data into a JsonDocument, and writes formatted (pretty-printed) JSON to a file.
  • Uses JsonDocumentOptions to specify that comments in the input JSON are allowed but ignored.
  • When finished, calls Flush on the writer. An alternative is to let the writer auto-flush when it's disposed.

Here's an example of JSON input to be processed by the example code:

{"Class Name": "Science","Teacher's Name": "Jane","Semester": "2019-01-01","Students": [{"Name": "John","Grade": 94.3},{"Name": "James","Grade": 81.0},{"Name": "Julia","Grade": 91.9},{"Name": "Jessica","Grade": 72.4},{"Name": "Johnathan"}],"Final": true}

The result is the following pretty-printed JSON output:

{
  "Class Name": "Science",
  "Teacher\u0027s Name": "Jane",
  "Semester": "2019-01-01",
  "Students": [
    {
      "Name": "John",
      "Grade": 94.3
    },
    {
      "Name": "James",
      "Grade": 81.0
    },
    {
      "Name": "Julia",
      "Grade": 91.9
    },
    {
      "Name": "Jessica",
      "Grade": 72.4
    },
    {
      "Name": "Johnathan"
    }
  ],
  "Final": true
}

JsonDocument is IDisposable

JsonDocument builds an in-memory view of the data into a pooled buffer. Therefore the JsonDocument type implements IDisposable and needs to be used inside a using block.

Only return a JsonDocument from your API if you want to transfer lifetime ownership and dispose responsibility to the caller. In most scenarios, that isn't necessary. If the caller needs to work with the entire JSON document, return the Clone of the RootElement, which is a JsonElement. If the caller needs to work with a particular element within the JSON document, return the Clone of that JsonElement. If you return the RootElement or a sub-element directly without making a Clone, the caller won't be able to access the returned JsonElement after the JsonDocument that owns it is disposed.

Here's an example that requires you to make a Clone:

public JsonElement LookAndLoad(JsonElement source)
{
    string json = File.ReadAllText(source.GetProperty("fileName").GetString());

    using (JsonDocument doc = JsonDocument.Parse(json))
    {
        return doc.RootElement.Clone();
    }
}

The preceding code expects a JsonElement that contains a fileName property. It opens the JSON file and creates a JsonDocument. The method assumes that the caller wants to work with the entire document, so it returns the Clone of the RootElement.

If you receive a JsonElement and are returning a sub-element, it's not necessary to return a Clone of the sub-element. The caller is responsible for keeping alive the JsonDocument that the passed-in JsonElement belongs to. For example:

public JsonElement ReturnFileName(JsonElement source)
{
   return source.GetProperty("fileName");
}

JsonDocument with JsonSerializerOptions

You can use JsonSerializer to serialize and deserialize an instance of JsonDocument. However, the implementation for reading and writing JsonDocument instances by using JsonSerializer is a wrapper over the JsonDocument.ParseValue(Utf8JsonReader) and JsonDocument.WriteTo(Utf8JsonWriter). This wrapper does not forward any JsonSerializerOptions (serializer features) to Utf8JsonReader or Utf8JsonWriter. For example, if you set JsonSerializerOptions.DefaultIgnoreCondition to WhenWritingNull and call JsonSerializer with an overload that takes JsonSerializerOptions, null properties won't be ignored.

The following example illustrates the result of using methods that take a JsonSerializerOptions parameter and serialize a JsonDocument instance:

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

namespace JsonDocumentWithJsonSerializerOptions;

public class Program
{
    public static void Main()
    {
        Person person = new Person { Name = "Nancy" };

        // Default serialization - Address property included with null token.
        // Output: {"Name":"Nancy","Address":null}
        string personJsonWithNull = JsonSerializer.Serialize(person);
        Console.WriteLine(personJsonWithNull);

        // Serialize and ignore null properties - null Address property is omitted
        // Output: {"Name":"Nancy"}
        JsonSerializerOptions options = new()
        {
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
        };
        string personJsonWithoutNull = JsonSerializer.Serialize(person, options);
        Console.WriteLine(personJsonWithoutNull);

        // Ignore null properties doesn't work when serializing JsonDocument instance
        // by using JsonSerializer.
        // Output: {"Name":"Nancy","Address":null}
        var personJsonDocument = JsonSerializer.Deserialize(personJsonWithNull);
        personJsonWithNull = JsonSerializer.Serialize(personJsonDocument, options);
        Console.WriteLine(personJsonWithNull);
    }
}
public class Person
{
    public string? Name { get; set; }

    public string? Address { get; set; }
}

If you need features of JsonSerializerOptions, use JsonSerializer with strongly typed targets (such as the Person class in this example) rather than JsonDocument.

Use Utf8JsonWriter

Utf8JsonWriter is a high-performance way to write UTF-8 encoded JSON text from common .NET types like String, Int32, and DateTime. The writer is a low-level type that can be used to build custom serializers. The JsonSerializer.Serialize method uses Utf8JsonWriter under the covers.

The following example shows how to use the Utf8JsonWriter class:

var options = new JsonWriterOptions
{
    Indented = true
};

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

writer.WriteStartObject();
writer.WriteString("date", DateTimeOffset.UtcNow);
writer.WriteNumber("temp", 42);
writer.WriteEndObject();
writer.Flush();

string json = Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(json);
Dim options As JsonWriterOptions = New JsonWriterOptions With {
    .Indented = True
}

Dim stream As MemoryStream = New MemoryStream
Dim writer As Utf8JsonWriter = New Utf8JsonWriter(stream, options)

writer.WriteStartObject()
writer.WriteString("date", DateTimeOffset.UtcNow)
writer.WriteNumber("temp", 42)
writer.WriteEndObject()
writer.Flush()

Dim json As String = Encoding.UTF8.GetString(stream.ToArray())
Console.WriteLine(json)

Write with UTF-8 text

To achieve the best possible performance while using the Utf8JsonWriter, write JSON payloads already encoded as UTF-8 text rather than as UTF-16 strings. Use JsonEncodedText to cache and pre-encode known string property names and values as statics, and pass those to the writer, rather than using UTF-16 string literals. This is faster than caching and using UTF-8 byte arrays.

This approach also works if you need to do custom escaping. System.Text.Json doesn't let you disable escaping while writing a string. However, you could pass in your own custom JavaScriptEncoder as an option to the writer, or create your own JsonEncodedText that uses your JavascriptEncoder to do the escaping, and then write the JsonEncodedText instead of the string. For more information, see Customize character encoding.

Write raw JSON

In some scenarios, you might want to write "raw" JSON to a JSON payload that you're creating with Utf8JsonWriter. You can use Utf8JsonWriter.WriteRawValue to do that. Here are typical scenarios:

  • You have an existing JSON payload that you want to enclose in new JSON.

  • You want to format values differently from the default Utf8JsonWriter formatting.

    For example, you might want to customize number formatting. By default, System.Text.Json omits the decimal point for whole numbers, writing 1 rather than 1.0, for example. The rationale is that writing fewer bytes is good for performance. But suppose the consumer of your JSON treats numbers with decimals as doubles, and numbers without decimals as integers. You might want to ensure that the numbers in an array are all recognized as doubles, by writing a decimal point and zero for whole numbers. The following example shows how to do that:

    using System.Text;
    using System.Text.Json;
    
    namespace WriteRawJson;
    
    public class Program
    {
        public static void Main()
        {
            JsonWriterOptions writerOptions = new() { Indented = true, };
    
            using MemoryStream stream = new();
            using Utf8JsonWriter writer = new(stream, writerOptions);
    
            writer.WriteStartObject();
    
            writer.WriteStartArray("defaultJsonFormatting");
            foreach (double number in new double[] { 50.4, 51 })
            {
                writer.WriteStartObject();
                writer.WritePropertyName("value");
                writer.WriteNumberValue(number);
                writer.WriteEndObject();
            }
            writer.WriteEndArray();
    
            writer.WriteStartArray("customJsonFormatting");
            foreach (double result in new double[] { 50.4, 51 })
            {
                writer.WriteStartObject();
                writer.WritePropertyName("value");
                writer.WriteRawValue(
                    FormatNumberValue(result), skipInputValidation: true);
                writer.WriteEndObject();
            }
            writer.WriteEndArray();
    
            writer.WriteEndObject();
            writer.Flush();
    
            string json = Encoding.UTF8.GetString(stream.ToArray());
            Console.WriteLine(json);
        }
        static string FormatNumberValue(double numberValue)
        {
            return numberValue == Convert.ToInt32(numberValue) ? 
                numberValue.ToString() + ".0" : numberValue.ToString();
        }
    }
    // output:
    //{
    //  "defaultJsonFormatting": [
    //    {
    //      "value": 50.4
    //    },
    //    {
    //      "value": 51
    //    }
    //  ],
    //  "customJsonFormatting": [
    //    {
    //      "value": 50.4
    //    },
    //    {
    //      "value": 51.0
    //    }
    //  ]
    //}
    

Customize character escaping

The StringEscapeHandling setting of JsonTextWriter offers options to escape all non-ASCII characters or HTML characters. By default, Utf8JsonWriter escapes all non-ASCII and HTML characters. This escaping is done for defense-in-depth security reasons. To specify a different escaping policy, create a JavaScriptEncoder and set JsonWriterOptions.Encoder. For more information, see Customize character encoding.

Write null values

To write null values by using Utf8JsonWriter, call:

  • WriteNull to write a key-value pair with null as the value.
  • WriteNullValue to write null as an element of a JSON array.

For a string property, if the string is null, WriteString and WriteStringValue are equivalent to WriteNull and WriteNullValue.

Write Timespan, Uri, or char values

To write Timespan, Uri, or char values, format them as strings (by calling ToString(), for example) and call WriteStringValue.

Use Utf8JsonReader

Utf8JsonReader is a high-performance, low allocation, forward-only reader for UTF-8 encoded JSON text, read from a ReadOnlySpan or ReadOnlySequence. The Utf8JsonReader is a low-level type that can be used to build custom parsers and deserializers. The JsonSerializer.Deserialize method uses Utf8JsonReader under the covers. The Utf8JsonReader can't be used directly from Visual Basic code. For more information, see Visual Basic support.

The following example shows how to use the Utf8JsonReader class:

var options = new JsonReaderOptions
{
    AllowTrailingCommas = true,
    CommentHandling = JsonCommentHandling.Skip
};
var reader = new Utf8JsonReader(jsonUtf8Bytes, options);

while (reader.Read())
{
    Console.Write(reader.TokenType);

    switch (reader.TokenType)
    {
        case JsonTokenType.PropertyName:
        case JsonTokenType.String:
            {
                string? text = reader.GetString();
                Console.Write(" ");
                Console.Write(text);
                break;
            }

        case JsonTokenType.Number:
            {
                int intValue = reader.GetInt32();
                Console.Write(" ");
                Console.Write(intValue);
                break;
            }

            // Other token types elided for brevity
    }
    Console.WriteLine();
}
' This code example doesn't apply to Visual Basic. For more information, go to the following URL:
' https://docs.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support

The preceding code assumes that the jsonUtf8 variable is a byte array that contains valid JSON, encoded as UTF-8.

Filter data using Utf8JsonReader

The following example shows how to synchronously read a file, and search for a value.

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

namespace SystemTextJsonSamples
{
    public class Utf8ReaderFromFile
    {
        private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name");
        private static ReadOnlySpan Utf8Bom => new byte[] { 0xEF, 0xBB, 0xBF };

        public static void Run()
        {
            // ReadAllBytes if the file encoding is UTF-8:
            string fileName = "UniversitiesUtf8.json";
            ReadOnlySpan jsonReadOnlySpan = File.ReadAllBytes(fileName);

            // Read past the UTF-8 BOM bytes if a BOM exists.
            if (jsonReadOnlySpan.StartsWith(Utf8Bom))
            {
                jsonReadOnlySpan = jsonReadOnlySpan.Slice(Utf8Bom.Length);
            }

            // Or read as UTF-16 and transcode to UTF-8 to convert to a ReadOnlySpan
            //string fileName = "Universities.json";
            //string jsonString = File.ReadAllText(fileName);
            //ReadOnlySpan jsonReadOnlySpan = Encoding.UTF8.GetBytes(jsonString);

            int count = 0;
            int total = 0;

            var reader = new Utf8JsonReader(jsonReadOnlySpan);

            while (reader.Read())
            {
                JsonTokenType tokenType = reader.TokenType;

                switch (tokenType)
                {
                    case JsonTokenType.StartObject:
                        total++;
                        break;
                    case JsonTokenType.PropertyName:
                        if (reader.ValueTextEquals(s_nameUtf8))
                        {
                            // Assume valid JSON, known schema
                            reader.Read();
                            if (reader.GetString()!.EndsWith("University"))
                            {
                                count++;
                            }
                        }
                        break;
                }
            }
            Console.WriteLine($"{count} out of {total} have names that end with 'University'");
        }
    }
}
' This code example doesn't apply to Visual Basic. For more information, go to the following URL:
' https://docs.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support

For an asynchronous version of this example, see .NET samples JSON project.

The preceding code:

  • Assumes the JSON contains an array of objects and each object may contain a "name" property of type string.

  • Counts objects and "name" property values that end with "University".

  • Assumes the file is encoded as UTF-16 and transcodes it into UTF-8. A file encoded as UTF-8 can be read directly into a ReadOnlySpan, by using the following code:

    ReadOnlySpan jsonReadOnlySpan = File.ReadAllBytes(fileName);
    

    If the file contains a UTF-8 byte order mark (BOM), remove it before passing the bytes to the Utf8JsonReader, since the reader expects text. Otherwise, the BOM is considered invalid JSON, and the reader throws an exception.

Here's a JSON sample that the preceding code can read. The resulting summary message is "2 out of 4 have names that end with 'University'":

[
  {
    "web_pages": [ "https://contoso.edu/" ],
    "alpha_two_code": "US",
    "state-province": null,
    "country": "United States",
    "domains": [ "contoso.edu" ],
    "name": "Contoso Community College"
  },
  {
    "web_pages": [ "http://fabrikam.edu/" ],
    "alpha_two_code": "US",
    "state-province": null,
    "country": "United States",
    "domains": [ "fabrikam.edu" ],
    "name": "Fabrikam Community College"
  },
  {
    "web_pages": [ "http://www.contosouniversity.edu/" ],
    "alpha_two_code": "US",
    "state-province": null,
    "country": "United States",
    "domains": [ "contosouniversity.edu" ],
    "name": "Contoso University"
  },
  {
    "web_pages": [ "http://www.fabrikamuniversity.edu/" ],
    "alpha_two_code": "US",
    "state-province": null,
    "country": "United States",
    "domains": [ "fabrikamuniversity.edu" ],
    "name": "Fabrikam University"
  }
]

Read from a stream using Utf8JsonReader

When reading a large file (a gigabyte or more in size, for example), you might want to avoid having to load the entire file into memory at once. For this scenario, you can use a FileStream.

When using the Utf8JsonReader to read from a stream, the following rules apply:

  • The buffer containing the partial JSON payload must be at least as large as the largest JSON token within it so that the reader can make forward progress.
  • The buffer must be at least as large as the largest sequence of white space within the JSON.
  • The reader doesn't keep track of the data it has read until it completely reads the next TokenType in the JSON payload. So when there are bytes left over in the buffer, you have to pass them to the reader again. You can use BytesConsumed to determine how many bytes are left over.

The following code illustrates how to read from a stream. The example shows a MemoryStream. Similar code will work with a FileStream, except when the FileStream contains a UTF-8 BOM at the start. In that case, you need to strip those three bytes from the buffer before passing the remaining bytes to the Utf8JsonReader. Otherwise the reader would throw an exception, since the BOM is not considered a valid part of the JSON.

The sample code starts with a 4KB buffer and doubles the buffer size each time it finds that the size is not large enough to fit a complete JSON token, which is required for the reader to make forward progress on the JSON payload. The JSON sample provided in the snippet triggers a buffer size increase only if you set a very small initial buffer size, for example, 10 bytes. If you set the initial buffer size to 10, the Console.WriteLine statements illustrate the cause and effect of buffer size increases. At the 4KB initial buffer size, the entire sample JSON is shown by each Console.WriteLine, and the buffer size never has to be increased.

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

namespace SystemTextJsonSamples
{
    public class Utf8ReaderPartialRead
    {
        public static void Run()
        {
            var jsonString = @"{
                ""Date"": ""2019-08-01T00:00:00-07:00"",
                ""Temperature"": 25,
                ""TemperatureRanges"": {
                    ""Cold"": { ""High"": 20, ""Low"": -10 },
                    ""Hot"": { ""High"": 60, ""Low"": 20 }
                },
                ""Summary"": ""Hot"",
            }";

            byte[] bytes = Encoding.UTF8.GetBytes(jsonString);
            var stream = new MemoryStream(bytes);

            var buffer = new byte[4096];

            // Fill the buffer.
            // For this snippet, we're assuming the stream is open and has data.
            // If it might be closed or empty, check if the return value is 0.
            stream.Read(buffer);

            // We set isFinalBlock to false since we expect more data in a subsequent read from the stream.
            var reader = new Utf8JsonReader(buffer, isFinalBlock: false, state: default);
            Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");

            // Search for "Summary" property name
            while (reader.TokenType != JsonTokenType.PropertyName || !reader.ValueTextEquals("Summary"))
            {
                if (!reader.Read())
                {
                    // Not enough of the JSON is in the buffer to complete a read.
                    GetMoreBytesFromStream(stream, ref buffer, ref reader);
                }
            }

            // Found the "Summary" property name.
            Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
            while (!reader.Read())
            {
                // Not enough of the JSON is in the buffer to complete a read.
                GetMoreBytesFromStream(stream, ref buffer, ref reader);
            }
            // Display value of Summary property, that is, "Hot".
            Console.WriteLine($"Got property value: {reader.GetString()}");
        }

        private static void GetMoreBytesFromStream(
            MemoryStream stream, ref byte[] buffer, ref Utf8JsonReader reader)
        {
            int bytesRead;
            if (reader.BytesConsumed < buffer.Length)
            {
                ReadOnlySpan leftover = buffer.AsSpan((int)reader.BytesConsumed);

                if (leftover.Length == buffer.Length)
                {
                    Array.Resize(ref buffer, buffer.Length * 2);
                    Console.WriteLine($"Increased buffer size to {buffer.Length}");
                }

                leftover.CopyTo(buffer);
                bytesRead = stream.Read(buffer.AsSpan(leftover.Length));
            }
            else
            {
                bytesRead = stream.Read(buffer);
            }
            Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
            reader = new Utf8JsonReader(buffer, isFinalBlock: bytesRead == 0, reader.CurrentState);
        }
    }
}
' This code example doesn't apply to Visual Basic. For more information, go to the following URL:
' https://docs.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support

The preceding example sets no limit to how large the buffer can grow. If the token size is too large, the code could fail with an OutOfMemoryException exception. This can happen if the JSON contains a token that is around 1 GB or more in size, because doubling the 1 GB size results in a size that is too large to fit into an int32 buffer.

Utf8JsonReader is a ref struct

Because the Utf8JsonReader type is a ref struct, it has certain limitations. For example, it can't be stored as a field on a class or struct other than a ref struct. To achieve high performance, this type must be a ref struct since it needs to cache the input ReadOnlySpan, which itself is a ref struct. In addition, this type is mutable since it holds state. Therefore, pass it by reference rather than by value. Passing it by value would result in a struct copy and the state changes would not be visible to the caller. For more information about how to use ref structs, see Write safe and efficient C# code.

Read UTF-8 text

To achieve the best possible performance while using the Utf8JsonReader, read JSON payloads already encoded as UTF-8 text rather than as UTF-16 strings. For a code example, see Filter data using Utf8JsonReader.

Read with multi-segment ReadOnlySequence

If your JSON input is a ReadOnlySpan, each JSON element can be accessed from the ValueSpan property on the reader as you go through the read loop. However, if your input is a ReadOnlySequence (which is the result of reading from a PipeReader), some JSON elements might straddle multiple segments of the ReadOnlySequence object. These elements would not be accessible from ValueSpan in a contiguous memory block. Instead, whenever you have a multi-segment ReadOnlySequence as input, poll the HasValueSequence property on the reader to figure out how to access the current JSON element. Here's a recommended pattern:

while (reader.Read())
{
    switch (reader.TokenType)
    {
        // ...
        ReadOnlySpan jsonElement = reader.HasValueSequence ?
            reader.ValueSequence.ToArray() :
            reader.ValueSpan;
        // ...
    }
}

Use ValueTextEquals for property name lookups

Don't use ValueSpan to do byte-by-byte comparisons by calling SequenceEqual for property name lookups. Call ValueTextEquals instead, because that method unescapes any characters that are escaped in the JSON. Here's an example that shows how to search for a property that is named "name":

private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name");
while (reader.Read())
{
    switch (reader.TokenType)
    {
        case JsonTokenType.StartObject:
            total++;
            break;
        case JsonTokenType.PropertyName:
            if (reader.ValueTextEquals(s_nameUtf8))
            {
                count++;
            }
            break;
    }
}

Read null values into nullable value types

The built-in System.Text.Json APIs return only non-nullable value types. For example, Utf8JsonReader.GetBoolean returns a bool. It throws an exception if it finds Null in the JSON. The following examples show two ways to handle nulls, one by returning a nullable value type and one by returning the default value:

public bool? ReadAsNullableBoolean()
{
    _reader.Read();
    if (_reader.TokenType == JsonTokenType.Null)
    {
        return null;
    }
    if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False)
    {
        throw new JsonException();
    }
    return _reader.GetBoolean();
}
public bool ReadAsBoolean(bool defaultValue)
{
    _reader.Read();
    if (_reader.TokenType == JsonTokenType.Null)
    {
        return defaultValue;
    }
    if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False)
    {
        throw new JsonException();
    }
    return _reader.GetBoolean();
}

See also

  • System.Text.Json overview
  • Instantiate JsonSerializerOptions instances
  • Enable case-insensitive matching
  • Customize property names and values
  • Ignore properties
  • Allow invalid JSON
  • Handle overflow JSON or use JsonElement or JsonNode
  • Preserve references and handle circular references
  • Deserialize to immutable types and non-public accessors
  • Polymorphic serialization
  • Migrate from Newtonsoft.Json to System.Text.Json
  • Customize character encoding
  • Use DOM, Utf8JsonReader, and Utf8JsonWriter
  • Write custom converters for JSON serialization
  • DateTime and DateTimeOffset support
  • How to use source generation
  • Supported collection types
  • System.Text.Json API reference
  • System.Text.Json.Serialization API reference

Feedback

Submit and view feedback for