System.Text.Json Serializing Hierarchical Data

The System.Text.Json serializer that was introduced with .NET Core 3.0 gets new features with every new .NET version. With .NET 7, features such as type hieararchies, contract customization, and source generator features have been added. This article shows using the JSON serializer with a hierarchy, and using a source generator.

Mountain Stream

The Model

The model that’s defined defines the types Game, and Move. The Game class specifies a few simple properties as well as a property of type ICollection that specifies a relationship. Using the primary constructor syntax, the C# compiler creates init-only properties with C# class records and a constructor that initializes the properties. The Moves property of type ICollection is initialized with a List. In this scneario, the collection contains types deriving from the abstract Move class.

The hierarchy is created with the Move type. The generic version Move derives from the non-generic abstract Move class. The Move class specifies a property of type ICollection that has a collection of fields of a single game move. The type TField is a generic parameter which allows using different game types. For example, a move can consist of a list of different colors, or with another game type, a list of different shapes and colors. The values of the Move class as well as the generic classes deriving from it should be written to the serialized JSON content.

The types that will be used for the generic type parameter TField are the records ColorField and ShapeAndColorField. ColorField wraps a color specified by a string, ShapeAndColorField wraps two strings specifying a shape and a color.

Models

To serialize the derived classes to JSON, the deserializer needs to know the types to create. To allow this, the JsonPolymorphic attribute is used to specify a key that is stored on serialization with the JSON content. With the sample code, the name $discriminator is used for the key. The values for this discriminator are specified with the JsonDerivedType attribute. JsonDerivedType is applied to the base class Move to specify what derived types should be serialized, and what discriminator value should be used with serialization. Using deserialization the value is used to create an instance of the mapped class.

> Because the Moves property of the Game class is needed with deserialization, the get accessor is not enough. In the code sample, a private init accessor together with the JsonInclude attribute is used.

JSON Serialization

The model types are in place, next let’s create and serialize instances with JsonSerializer.Serialize. The serialization is customized passing JsonSerializerOptions. With the sample code, the naming policy is set to camel case which is the same configuration used from a Web API. For a nicer output on the console, write indented is configured. Invoking JsonSerializer.Deserialize, .NET objects are created from JSON:

JSON Serialization

Checking the output information, you can see the $discriminator and the values used specified from the attribute:

JSON output

JSON Source Generator

To reduce reflection and instead generate source code at compile time, the JSON source generator can be used. This generator is activated by creatign a partial class that derives from the base class JsonSerializerContext, and annoatating it with the JsonSourceGenerationOptions. The GamesContext class in the following code snippet uses the same settings as the JsonSerializerOptions with the previous sample.

JSON Source Generator Context

Contrary to the previous sample using properties with init accessors, this is not possible with the .NET 7 JSON source generator. The .NET 7 source generator doesn’t support init accessors and required modifiers. Using preview 1 of .NET 8, this is already possible. See a link below for upcoming System.Text.Json updates with .NET 8.

To use the generated source code, the context needs to be passed as an argument of the Serialize and Deserialize methods as shown in the next code snippet. The GamesContext.Default property is created from the source generator.

Use the source generator

To check the generated code, you can open the Visual Studio Solution Explorer, open Analyzers bewlow Dependencies, and expand the System.Text.Json.SourceGeneration. Here you find how all the different attributes apply to source code.

Source generator generated code

Take away

While the first version of System.Text.Json was very fast, it was limited with its features. Every .NET version since then, the JSON serializer has been enhanced. With .NET 7 it supports a hierarchy of classes by applying simple attributes. This serializer reduces memory needs and enhances performance by using the Span type. With the source generator, more performance imporovements are possible, and the way gets opened for using AOT compilation with .NET 8.

Enjoy learning and programming!

Christian

If you like this article, please buy Christian a cup of coffee by going here. Thanks!

Buy Me A Coffee

More Information

More information about programming EF Core is available in my book and my workshops.

Read more about files, streams, and JSON serialization in my book Professional C# and .NET – 2021 Edition

See Chapter 18, "Files and Streams".

System.Text.Json Work planned for .NET 8

Trainings

Sample source code

The complete source code of this sample is available on Professional C# source code – see the folders 5_More/FilesAndStreams/JsonInheritance and JsonInheritanceWithSourceGenerator

Thank you for your download Get a free download for crediting the author! [Photo Refreshing Mountain Stream 3147742 © Psychocy] | Dreamstime.com]()

Photo 3147742 © Psychocy

4 thoughts on “System.Text.Json Serializing Hierarchical Data

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.