DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,673 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Lucas Paganini
Lucas Paganini

Posted on • Originally published at lucaspaganini.com

Discriminated Unions or Tagged Unions Types

TypeScript Narrowing #4


See this and many other articles at lucaspaganini.com

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.

Tagged 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.

type Log = Warning | Debug | Information;

interface Warning {
  text: string;
}

interface Debug {
  message: string;
}

interface Information {
  msg: string;
}
Enter fullscreen mode Exit fullscreen mode

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.

type Log = Warning | Debug | Information;

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

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

interface Information {
  subscribeToTheNewsletter: 'share';
  msg: string;
}
Enter fullscreen mode Exit fullscreen mode

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

Discriminated 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.

Type 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.

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 => { ... }
Enter fullscreen mode Exit fullscreen mode

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.

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
}
Enter fullscreen mode Exit fullscreen mode

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

let value: Log;

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

Conclusion

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.

Related content

  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

References

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

Top comments (0)

πŸ‘‹ Hey, my name is Noah and I’m the one who set up this ad. My job is to get you to join DEV, so if you fancy doing me a favor, I’d love for you to create an account.

If you found DEV from searching around, here are a couple of our most popular articles on DEV: