TypeScript compile-time interface validation with tagged unions
Tags: engineeringmonday, typescript, react, tutorial
Published: 2020-03-09 09:09:09

---
title: TypeScript compile-time interface validation with tagged unions
published: true
description: Tagged unions are an advanced TypeScript pattern which can help prevent runtime bugs in compile time
tags: engineeringmonday, typescript, react, tutorial
---

If you work with TypeScript, you probably love the static type checking it provides. It’s a very powerful programming tool that helps us detect bugs before they can even run, by showing us compile errors. 

Using interfaces is by itself very effective technique for writing code that is correct by a specified contract. But, what if we have similar contracts that define some intrinsic behaviour of a class or a function and we want to combine their definitions?


Let's image we're building a table with React that shows a list of records from the backend API. The component receives `columnData` of type `TableColumnData[]` as a prop, which is an array of config objects that determine how each column should be built and how it should behave. The content of the column is specified with the `dataIndex` field of the interface, which renders the value of matching key from each record passed to the table. As the data is passed from the backend API, it can have thousands of records, therefore we need to add the ability to search only the ones we need. 

We can then add a filter property on the column that will, if not left empty, render a text input in our column header. By submitting the search, the table will do a request on the API with the new value of the specified field.

The overly simplified version of the `TableColumnData` interface could look like this:

```typescript
export interface TableColumnData {
    dataIndex: string;
    title: string;
    filter?: TableColumnFilter;
}
```
and the interface for the filter can be specified like this:

```typescript
export interface TableColumnFilter {
    field: string;
}
```

Finally, table should be used similarly to this (pseudo-react-code):
```typescript
const MyTable: React.FC = (props) => {
    const columnData = [
        {
            title: "name",
            dataIndex: "name",
            filter: {
                field: "name",
            },
        },
        {
            title: "birthday",
            dataIndex: "birthday",
        },
    ];

    return <ResourceTable {...props} columnData={columnData} />;
}
```  


The filtering made the user experience of our table richer, but what if we want to add new types of filters, such as, for example, date filter? 

We can create another interface for that filter type, rename the TableColumnFilter to TableColumnTextFilter and combine the two filter types together in a union.

```typescript
export interface TableColumnDateFilter {
    field: string;
    showHours: boolean;
}
```

Now, TableColumnFilter can be defined like this:

```typescript
export type TableColumnFilter = TableColumnTextFilter | TableColumnDateFilter;
```

Our table still works, but now there is no way of knowing we used the proper interface for the filter type.

```typescript
const MyTable: React.FC = (props) => {
    const columnData = [
        {
            title: "name",
            dataIndex: "name",
            filter: {
                field: "name",
                // does not make much sense
                showHours: true,
            },
        },
        {
            title: "birthday",
            dataIndex: "birthday",
            filter: {
                field: "birthday",
            },
        },
    ];

    return <ResourceTable {...props} columnData={columnData} />;
}
```
We can then narrow the types further by creating an enum. That enum will tell the TypeScript compiler which filter type is used, and therefore it will hint us what the rest of the interface should look like.

```typescript
export enum ColumnFilterType {
    Text = "text",
    Date = "date",
}

export interface TableColumnTextFilter {
    type: ColumnFilterType.Text;
    field: string;
}


export interface TableColumnDateFilter {
    type: ColumnFilterType.Date;
    field: string;
    showHours: boolean;
}
```


This pattern is called **discriminated union**, aka **tagged union** or **algebraic data type**. 

In our scenario, the *discriminant* is the type field, which will be used to differentiate the types.

Now, expanding our table example with the type field, we get a compile error when using enums. 

```typescript
const MyTable: React.FC = (props) => {
    const columnData = [
        {
            title: "name",
            dataIndex: "name",
            filter: {
                field: "name",
                showHours: true,
                type: ColumnFilterType.Text,
            },
        },
        {
            title: "birthday",
            dataIndex: "birthday",
            filter: {
                field: "birthday",
                type: ColumnFilterType.Date,
            },
        },
    ];

    return <ResourceTable {...props} columnData={columnData} />;
}
```

The error is `Type 'ColumnFilterType' is not assignable to type 'ColumnFilterType.Date'`. This is expected as TypeScript thinks we use `ColumnFilterType` as a value for the type field.

We can prevent this by using [const assertion](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) and prevent further type widening.

```typescript
const MyTable: React.FC = (props) => {
    const columnData = [
        {
            title: "name",
            dataIndex: "name",
            filter: {
                field: "name",
                type: ColumnFilterType.Text as const,
            },
        },
        {
            title: "birthday",
            dataIndex: "birthday",
            filter: {
                field: "birthday",
                type: ColumnFilterType.Date as const,
                showHours: true,
            },
        },
    ];

    return <ResourceTable {...props} columnData={columnData} />;
}
```

Now, using the interface incorrectly will result in a compile error, which may help you prevent runtime errors if that internal behaviour is determined by a correct interface. To me, this ability to have pre-compile validation of implementation is what makes typed languages really stand out. They are especially helpful in collaboration and refactoring.

Have you had a chance to used tagged unions before? Do you have a TypeScript feature that you could not live without? Share those in the comments below! ✌🏻