Mimic Discriminated Union Types in C# with serialization via System.Text.Json

We all know there’s no discriminated union types in C# but if you work with languages like TypeScript you’re already familiar with them. Let model a simple discriminated union using TypeScript:

In C# we have to use a more verbose approach to achieve the same effect due to the nominal type system but after all it’s possible. That’s how the union type above can be implemented using C#:

Let’s try to serialize an instance of this union type using System.Text.Json. In order to produce an instance of one of the Status subclasses I created a basic factory called StatusFactory:

If you look at the output you’ll see an unexpected result. There are some properties missing (like “result” in the complete object):

{"kind":"InProgress"}
{"kind":"Complete"}
{"kind":"Failure"}

This happens because the System.Text.Json serializer doesn’t distinguish concrete types and serialize any instance of the Status subclasses as a general Status object. Indeed the System.Text.Json polymorphic serialization is quite limited.

For proper serialization of our union type we have to implement a custom JSON converter which will be aware of union types. But first there must be distinct signs that a given type is a discriminated union or not. The first sign of a discriminated union is a presence of the type discriminator and we also have to hint the serializer which subclasses are known union type cases. Let’s create special attributes to mark our union type for serialization:

UnionTagAttribute will be used to mark the type discriminator and UnionCaseAttribute to mark known union case classes. There is the union type Status after all attributes attached:

Now we can implement a JSON converter that will be aware of union types. We also make this converter work with generic classes.

And before we’ll get ready for serialization/deserialization of our union type let’s write a JSON converter factory that will automatically recognize all new union type and register a concrete JSON converter for them:

Now wrap everything together and check that all work as expected:

You will see the valid results. First three line are the results of serialization and rest are the results of deserialization. I use https://www.nuget.org/packages/ObjectDumper.NET/ to print objects’ states to the console:

{"kind":"InProgress","started":"2021-01-05T14:50:25.185305Z"}
{"kind":"Complete","result":"Expected data"}
{"kind":"Failure","errorCode":404}
{InProgressStatus}
Kind: "InProgress"
Started: 01/05/2021 14:50:25
{CompleteStatus}
Kind: "Complete"
Result: "Expected data"
{FailureStatus}
Kind: "Failure"
ErrorCode: 404

P.S.: Hope you find this article helpful.

P.P.S: You can find the full code listing here: https://gist.github.com/shadeglare/6b46baa340346e575b2751475733405c#file-complete-cs

P.P.P.S: Or use the nuget package https://github.com/hexarc-software/hexarc-serialization, https://www.nuget.org/packages/Hexarc.Serialization.Union/