Danny Guo | 郭亚东

How to Enforce Exhaustive TypeScript Enum Mappings Using Records

 ·  349 words  ·  ~2 minutes to read

Instead of using a function to exhaustively map a TypeScript enum to other values, you can use a Record type. This turns a runtime error into a compile-time error.

For example (and you can try out these examples with this Replit), let’s say you have an enum of countries, and you want a way to get the capital for each country. You might write a function to do so.

enum Country {
    Australia = "Australia",
    China = "China",
    UnitedStates = "United States"
}

function getCountryCapital(country: Country) {
    if (country === Country.Australia) {
        return "Canberra";
    } else if (country === Country.China) {
        return "Beijing";
    } else if (country === Country.UnitedStates) {
        return "Washington, D.C.";
    }
}

This does the job, but what if someone adds a new Country value without updating getCountryCapital? Then the function would return undefined, which could cause bad behavior. One option is to throw an error.

function getCountryCapital(country: Country): string {
    if (country === Country.Australia) {
        return "Canberra";
    } else if (country === Country.China) {
        return "Beijing";
    } else if (country === Country.UnitedStates) {
        return "Washington, D.C.";
    }

    throw new Error(`Unexpected country: ${country}`);
}

So you would at least get a runtime error as early as possible. But you can do better in terms of safety by using a Record, which turns this situation into a compile-time error.

const CountryToCapital: Record<Country, string> = {
    [Country.Australia]: "Canberra",
    [Country.China]: "Beijing",
    [Country.UnitedStates]: "Washington, D.C."
}

Let’s say you add South Africa to the enum without updating CountryToCapital.

enum Country {
    Australia = "Australia",
    China = "China",
    SouthAfrica = "South Africa",
    UnitedStates = "United States"
}

TypeScript should then give you an error. And if your IDE or editor is connected to TypeScript, you should see a hint for the error in your code as well.

Property '"South Africa"' is missing in type '{ Australia: string; China:
string; "United States": string; }' but required in type 'Record<Country,
string>'.

Unions

This approach works for unions as well. Adding South Africa to this union would also result in a type error.

type Country = "Australia" | "China" | "United States";

const CountryToCapital: Record<Country, string> = {
    "Australia": "Canberra",
    "China": "Beijing",
    "United States": "Washington, D.C."
}

← Why I Blog Forex Trading for Fun and Luckily Profit →

Follow me on Twitter or Mastodon or subscribe to my newsletter or RSS feed for future posts.

Found an error or typo? Feel free to open a pull request on GitHub.