04.11.2022 - TypeScript/Use String As Enum Key

All posts

Posted On 04.11.2022

In TypeScript, you can define an enum and access its value with a string-like value, like this:

enum Editor {
    VIM = 'Neovim',
    EMACS = 'Emacs',
    VSCODE = 'Visual Studio Code'
};
 
const key = 'VSCODE';
const editor = Editor[key];

In this example, the type of the key variable is not actually a string, but a literal type called ‘VSCODE’.

If you change the key definition from const to letor give it a type annotation, you will actually see that a string cannot be used to access enum values:

const key: string = 'VSCODE';
// or
let key = 'VSCODE';
 
const editor = Editor[key]; // ERROR!
//             ^^^^^^^^^^^^
// Element implicitly has an 'any' type because expression
// of type 'string' can't be used to index type 'typeof Editor'.
// No index signature with a parameter of type 'string' was
// found on type 'typeof Editor'.(7053)

There are many cases where you want to access your enum values with a string key, for example, when the key need to be calculated based on some constraints:

const getEditor = (needMouse?: boolean): string => {
    return needMouse ? 'VSCode' : 'Vim';
}
 
const editor = Editor[getEditor(true).toUpperCase()];

Or when you want to map your enum to some other data structures, and still want to refer to that enum values:

const editorList = Object.keys(Editor)
    .filter(key => key !== 'EMACS')
    .map(key => ({
        editorCode: key,
        displayName: Editor[key]
    }));
 
// Expected an array of:
// [
//    {
//        editorCode: 'VIM',
//        displayName: 'Neovim'
//    },
//    {
//        editorCode: 'VSCODE',
//        displayName: 'Visual Studio Code'
//    }
// ]

The values of the Editor enum can only be accessed by some keys of the union type “VIM” | “EMACS” | “VSCODE”. To make the above two example works, we need to annotate the key variable as the union “VIM” | “EMACS” | “VSCODE”.

You can define this union in two ways:

type EditorKey = "VIM" | "EMACS" | "VSCODE";
 
// or
 
type EditorKey = keyof typeof Editor;

Now we can use type assertion to convince TypeScript that key is a value of type EditorKey, and it should be OK to compile your code:

const editorList = Object.keys(Editor)
    .filter(key => key !== 'EMACS')
    .map(key => ({
        editorCode: key,
        displayName: Editor[key as EditorKey] // THIS!
    }));