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.
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<Move>
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<Move>
is initialized with a List<Move>
. 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<T>
derives from the non-generic abstract Move
class. The Move<TField>
class specifies a property of type ICollection<TField>
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.
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 theGame
class is needed with deserialization, theget
accessor is not enough. In the code sample, aprivate init
accessor together with theJsonInclude
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:
Checking the output information, you can see the $discriminator
and the values used specified from the attribute:
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.
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.
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.
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!

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
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]()