Explaining System.Text.Json in .NET

Introduction

The System.Text.Json library in .NET9 has been significantly enhanced to provide developers with more robust and flexible JSON processing capabilities. These improvements focus on JSON schema support, intelligent application features, and increased customization options for serialization and deserialization processes.

There are other .NET9 features discussed in my earlier articles. The following articles also contain sample source code projects attached for easy understanding and illustration point of view.

  1. Understanding UUID v7 in .NET 9
  2. Semi Auto Implemented Properties in .NET 9
  3. Overload Resolution Priority in .NET 9
  4. Open API Documentation and Swagger alternatives in .NET 9
  5. Understanding Escape Characters in .NET

Agenda

In this article, we will understand most of the features supported in System.Text.Json Library. Those details are,

  • Json Schema Exporter
  • Nullable Reference Type Support
  • Customizing Serialization Indentation
  • JsonSerializerOptions.Web
  • JsonObject Property Ordering
  • JsonElement DeepEquals Method
  • Customizing Enum Member Names

Json Schema Exporter

A notable addition is the JsonSchemaExporter class, which enables the extraction of JSON schema documents from .NET types. This feature facilitates the validation and documentation of JSON data structures, ensuring consistency across applications. The code snippet below helps to get the schema format as output.

Employee class file

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int YearJoin { get; set; }
}

The below code helps to get the schema as output.

public static void JsonExporterExample()
{
    JsonSerializerOptions options = JsonSerializerOptions.Default;
    JsonNode schema = options.GetJsonSchemaAsNode(typeof(Employee));
    Console.WriteLine(schema.ToString());
}

The output schema defines the structure of the Employee class, specifying property types and nullability, which aids in maintaining data integrity.

Structure of the Employee class

Nullable Reference Type Support

To align with C#'s nullable reference type annotations, System.Text.Json now offers the RespectNullableAnnotations option. When enabled, the serializer enforces non-nullable reference types during serialization and deserialization, throwing exceptions if a non-nullable property is assigned a null value.

So, the code below shows a clear example of this property usage and its results.

public static void JsonRespectNullableAnnotations()
{
    var emp = new Employee { Id = 1, Name = null, YearJoin = 2024 };

    //Print the emp object WITHOUT applying Nullable notation
    var empObject = JsonSerializer.Serialize(emp);            
    Console.WriteLine(empObject);

    //Set Serailization options with Nullable notation as true
    JsonSerializerOptions options = new()
    {
        RespectNullableAnnotations = true
    };

    try
    {
        //emp object searialiization WITH applying Nullable notation
        var empObjectNotation = JsonSerializer.Serialize(emp, options);
        // Throws System.Text.Json.JsonException
    }
    catch (Exception ex)
    {
        Console.WriteLine("Serialization error :: {0}", ex.Message);
    }
    

    try
    {
        JsonSerializer.Deserialize<Employee>("""{"Id":1,"Name":null,"YearJoin":2024}""", options);
        // Throws System.Text.Json.JsonException
    }
    catch (Exception ex)
    {
        Console.WriteLine("De Serialization error :: {0}", ex.Message);
    }
    
}

The output of the aforesaid code is as follows.

JSON Null Notation

Customizing Serialization Indentation

To offer more flexibility in JSON formatting, System.Text.Json introduces options to customize indentation. We can specify the character and size used for indentation, allowing the JSON output to meet specific formatting requirements. This helps to read/beautify the json in an easy and understandable way.

IndentCharacter only supports space and horizontal tab formats only.

public static void JsonIdentation()
{
    var emp = new Employee { Id = 1, Name = "Ramchand", YearJoin = 2024 };
    
    //print the object WITH OUT indentation
    string empObject = JsonSerializer.Serialize(emp);
    Console.WriteLine(empObject);

    var options = new JsonSerializerOptions
    {
        WriteIndented = true,
        IndentCharacter = '\t',
        IndentSize = 1
    };
    //print the object WITH indentation
    string empObjectOptions = JsonSerializer.Serialize(emp, options);
    Console.WriteLine(empObjectOptions);
}

The output screen is as follows.

JSON Identation

JsonSerializerOptions.Web

The introduction of JsonSerializerOptions.Web provides a predefined set of options tailored for web applications. This includes settings like camel-casing of property names and flexible number handling, aligning JSON serialization with common web API practices.

public static void JsonSearializeWeb()
{
    var emp = new Employee { Id = 1, Name = "Ramchand", YearJoin = 2024 };
    var empWebJson = JsonSerializer.Serialize(emp, JsonSerializerOptions.Web);
    Console.WriteLine(empWebJson);
}

The output screen is as follows. We can observe that attributes are formatted in came Case now.

JSON Serialize web

JsonObject Property Ordering

The JsonObject class now allows developers to control the order of properties in JSON objects. This is particularly beneficial when the property order is significant, such as in certain serialization scenarios or when interfacing with systems that are sensitive to property ordering.

public static void JsonObjectOrdering()
{
    JsonObject emp = new(){ ["Id"] = 1, ["Name"] = "Ramchand", ["YearJoin"] = 2024 };
    emp.Insert(0, "Org", "ABC Ltd");
    Console.WriteLine(emp);
}

The output screen is as follows.

JSON Object ordering

JsonElement DeepEquals Method

In .NET 9, the System.Text.Json library introduces the JsonElement.DeepEquals method, enabling deep comparison between two JsonElement instances. This method determines if two JSON elements are structurally and semantically identical.

public static void JsonDeepEquals()
{
    var empJson = """{"Id":1,"Name":"Ramchand","YearJoin":2024}""";
    JsonElement left = JsonDocument.Parse(empJson).RootElement;
    JsonElement right = JsonDocument.Parse(empJson).RootElement;

    //Prints - TRUE as both the elments have same elements
    Console.WriteLine(JsonElement.DeepEquals(left, right));

    var empJson1 = """{"Id":1,"FullName":"Ramchand","YearJoin":2024}""";
    right = JsonDocument.Parse(empJson1).RootElement;
    //Prints - FALSE as the elments have different elements data.
    Console.WriteLine(JsonElement.DeepEquals(left, right));

}

Prior to .NET 9, developers often relied on external libraries like Newtonsoft.Json's JToken.DeepEquals for deep JSON comparisons. The inclusion of JsonElement.DeepEquals in System.Text.Json provides a built-in solution, reducing the need for third-party dependencies.

Customizing Enum Member Names

In .NET 9, The System.Text.Json library introduces the JsonStringEnumMemberName attribute, allowing us to customize the JSON representation of individual enum members. This enhancement provides greater flexibility when serializing enums, especially in scenarios where specific naming conventions or formats are required.

To customize the JSON output of an enum member, apply the JsonStringEnumMemberName attribute to the desired enum fields. Ensure that the enum is decorated with the [JsonConverter(typeof(JsonStringEnumConverter))] attribute to enable string-based serialization.

[Flags]
[JsonConverter(typeof(JsonStringEnumConverter))]
enum EmpState
{
    OnBoarding = 1,

    [JsonStringEnumMemberName("In Progress")]
    InProgress = 2,

    [JsonStringEnumMemberName("Ready For Dev")]
    ReadyForDev = 3
}

public static void JsonEnumTypes()
{
    var state = EmpState.ReadyForDev;
    var empJson = JsonSerializer.Serialize(state);
    Console.WriteLine(empJson);
}

In this example, the ReadyForDev enum member is serialized as "Ready For Dev" in the JSON output, as specified by the JsonStringEnumMemberName attribute.

Benefits of This Enhancement

  • Consistency: Aligns JSON output with specific naming conventions required by external systems or APIs.
  • Clarity: Provides more descriptive or user-friendly names in the serialized JSON, improving readability.
  • Compatibility: Ensures that the JSON output matches expected formats without altering the underlying enum definitions.

Considerations

  • Deserialization: When customizing enum member names, ensure that the JSON input during deserialization matches the specified names to avoid errors.
  • Flags Attribute: For enums decorated with the [Flags] attribute, the JsonStringEnumConverter correctly handles combined flag values, serializing them as a comma-separated list of names.

Summary

.NET 9 brings several improvements to the System.Text.Json library, making it more powerful, flexible, and performant for JSON serialization and deserialization. The aforesaid discussed source code examples are attached as a JsonFeaturesDemo.zip file in this article.

Please see the below reference for more details.

Reference: https://learn.microsoft.com/en-us/dotnet/api/system.text.json?view=net-9.0

Up Next
    Ebook Download
    View all
    Learn
    View all