DEV Community

Cover image for How to Deserialize Internally Tagged JSON in C# with AoT compilation
Sby
Sby

Posted on

1

How to Deserialize Internally Tagged JSON in C# with AoT compilation

In this quick tutorial we'll see how to set up JSON serialization and deserialization for an internally tagged object in C# System.Text.Json using AoT-friendly polymorhpism source generators.
Unlike reflection and writing a custom converter, source generators makes this both easy and performant.

1. Declare Context

First, you need a class holding the context for your serialization and deserialization for which source will be generated.
For that, you need to inherit from JsonSerializerContext and declare your options with JsonSourceGenerationOptions.

Then you need to declare which types you want to serialize and deserialize. Here we have ItemModel base class and TextItem and ImageItem derived classes. Using typeof(List<ItemModel>) will allow for deserializing both a single ItemModel object and a list of ItemModels.

[JsonSourceGenerationOptions(WriteIndented = false)]
[JsonSerializable(typeof(List<TextItem>))]
[JsonSerializable(typeof(List<ImageItem>))]
[JsonSerializable(typeof(List<ItemModel>))]
internal partial class SourceGenerationContext : JsonSerializerContext { }
Enter fullscreen mode Exit fullscreen mode

2. Create Type Discriminators

You need a way to tell JSON deserializer that your base class has derived implementations.
For that we'll use JsonPolymorphic and JsonDerivedType metadata on the base class.

[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(TextItem), typeDiscriminator: "text")]
[JsonDerivedType(typeof(ImageItem), typeDiscriminator: "image")]
public abstract class ItemModel { }
Enter fullscreen mode Exit fullscreen mode

C# uses type disciminator property "$type" by default but here we're declaring it explicitly.
Then we point to the derived types and give the type discriminator "text" and "image". However, you're not limited to using strings here and can use numbers to, for example, save on size.

2.1. Optional. Serializing Derived Types

While serializing and deserializing ItemModel will work as intended, serializing TextItem and ImageItem directly will not generate a type discriminator, which is something you may want to do.

For that we can simply configure the derived type to be considered derived from itself.

[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(TextItem), typeDiscriminator: "text")]
public class TextItem : ItemModel { }
Enter fullscreen mode Exit fullscreen mode

Now we have repeating strings in base class and derived class, which can be refactored into constants.

internal static class Constants
{
    public const string Discriminator = "$type";
    public const string TextDiscriminator = "text";
    public const string ImageDiscriminator = "image";
}

[JsonPolymorphic(TypeDiscriminatorPropertyName = Constants.Discriminator)]
[JsonDerivedType(typeof(TextItem), typeDiscriminator: Constants.TextDiscriminator)]
[JsonDerivedType(typeof(ImageItem), typeDiscriminator: Constants.ImageDiscriminator)]
public abstract class ItemModel { }
Enter fullscreen mode Exit fullscreen mode

3. Pass the Context

Now all we need to do is pass the generated context to the serializer/deserializer.

var items = await JsonSerializer.DeserializeAsync<List<ItemModel>>(
    stream,
    SourceGenerationContext.Default.ListItemModel
) ?? [];
Enter fullscreen mode Exit fullscreen mode
await JsonSerializer.SerializeAsync(
    stream,
    images,
    SourceGenerationContext.Default.ListImageItem
);
Enter fullscreen mode Exit fullscreen mode

Conclusion

And that's it for this tutorial on how to work with internally tagged polymorphic JSON objects in C#.
Feel free to share thoughts and questions in the comments

👋 While you are here

Reinvent your career. Join DEV.

It takes one minute and is worth it for your career.

Get started

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay