Discriminated Unions or Tagged Unions Types - TypeScript Narrowing #4

TypeScript Narrowing #4

Heeeello and welcome to the fourth article in our TypeScript narrowing series. We're continuing from where we left off, so If you've missed any of the previous articles, I suggest you go back and read them.

In this article, we will cover tagged union types (AKA discriminated unions).

And we have more to come, in future articles:

  1. Assertion guards
  2. Higher-order guards
  3. Narrowing library

I'm Lucas Paganini, and on this site, we release web development tutorials. Subscribe to the newsletter if you're interested in that.

LinkTagged Union Types (Discriminated Unions)

So, tagged union types... what are they?

It's pretty self-explanatory. They are union types with a tag πŸ˜…

For example, you could have a union type called Log that aggregates three interfaces: Warning, Debug, and Information.

TypeScript
type Log = Warning | Debug | Information;

interface Warning {
  text: string;
}

interface Debug {
  message: string;
}

interface Information {
  msg: string;
}

Cool, now we have a union type. To turn that into a tagged union type, we need a common property with literal types between our interfaces. It could be any property name, but to simulate a real-world example, we'll call it .subscribeToTheNewsletter.

This property will serve as an ID for the different types of interfaces that make up the Log type. Every interface will have a different literal type for that property.

TypeScript
type Log = Warning | Debug | Information;

interface Warning {
  subscribeToTheNewsletter: 'like';
  text: string;
}

interface Debug {
  subscribeToTheNewsletter: 'comment';
  message: string;
}

interface Information {
  subscribeToTheNewsletter: 'share';
  msg: string;
}

And that's it. The .subscribeToTheNewsletter property is serving as a tag for the Log type, so it is now a tagged union type.

LinkDiscriminated Union Terminology

As I've mentioned before, this is also known as a discriminated union, and our .subscribeToTheNewsletter property is the discriminant property of Log.

To quote from the TypeScript docs: "When every type in a union contains a common property with literal types, TypeScript considers that to be a discriminated union."

From now on, I'll use the "discriminated unions" terminology, instead of "tagged union types" because that's how TypeScript is calling it nowadays and I want to be consistent with the official terminologies – even though I believe "tagged union types" would be a better name for it.

LinkType Guards for Discriminated Unions

"Ok Lucas, I got it. But what's the point? What do I get to do with a discriminated union?"

Good question.

What you get with a discriminated union is a type guard for all the possible discriminations of the union type.

For example, if you have a variable called value that is a Log. That means that value could be either a Warning, a Debug or an Information.

TypeScript
let value: Log;

if (isWarning(value)) {
  // Handle the Warning case
} else if (isDebug(value)) {
  // Handle the Debug case
} else if (isInformation(value)) {
  // Handle the Information case
}

const isWarning =
  (value: unknown): value is Warning => { ... }

const isDebug =
  (value: unknown): value is Debug => { ... }

const isInformation =
  (value: unknown): value is Information => { ... }

But instead of creating individual guards for those three interfaces, you could just check the value of the discriminant property.

If .subscribeToTheNewsletter is "like", you know that it's a Warning. If it's "comment", it's a Debug. And if it's "share", it's an Information.

TypeScript
let value: Log;

if (value.subscribeToTheNewsletter === 'like') {
  // Handle the Warning case
} else if (value.subscribeToTheNewsletter === 'comment') {
  // Handle the Debug case
} else if (value.subscribeToTheNewsletter === 'share') {
  // Handle the Information case
}

And you can make your code even clearer by going an extra mile and switching your if statements – got it? πŸ˜‰ "switching"... "switch"... no? ok...

TypeScript
let value: Log;

switch (value.subscribeToTheNewsletter) {
  case 'like':
  // Handle the Warning case
  case 'comment':
  // Handle the Debug case
  case 'share':
  // Handle the Information case
}

LinkConclusion

References and links for the previous articles are in the references.

If you enjoyed the content, you know what to do.

And if your company is looking for remote web developers, consider contacting me and my team. You can do so on lucaspaganini.com.

Have a great day, and I'll see you soon.

  1. TypeScript Narrowing Part 1 - What is a Type Guard
  2. TypeScript Narrowing Part 2 - Fundamental Type Guards
  3. TypeScript Narrowing Part 3 - Custom Type Guards

LinkReferences

  1. TypeScript docs on narrowing
  2. TypeScript docs on discriminated unions

Join the newsletter and be the first to know when I launch a course, post a video or write an article.

I don't SPAM, one email per week tops.