How to build a type from enum values in TypeScript?
Asked Answered
B

4

93

Given the following:

enum FooKeys {
  FOO = 'foo',
  BAR = 'bar',
}

I'd like to make an interface like this one, but instead of defining keys by hand, build it out of enum's values.

interface Foo {
  foo: string
  bar: string
}

Is something like this possible with TypeScript?

Thanks!

Before answered 13/3, 2019 at 12:41 Comment(0)
E
101

Yes, you can use enum values as keys. And you can use a mapped type like the standard library's Record<K, V> to prevent repetition:

enum FooKeys {
  FOO = 'foo',
  BAR = 'bar',
}

// probably all you need, but it's a type alias
type FooType = Record<FooKeys, string>;

// if you need an interface instead you can do this
interface FooInterface extends FooType {};

And you can verify that it works:

declare const foo: FooInterface;
foo.foo; // okay
foo[FooKeys.FOO]; // okay

foo.bar; // okay
foo[FooKeys.BAR]; // okay

foo.baz; // error

Does that work for you? Good luck!

Eulogistic answered 13/3, 2019 at 13:27 Comment(3)
Thank you for a detailed answer. This is exactly what I needed!Before
What is Record in Record<FooKeys, string>? Why did you decide to add yet another arbitrary type in your app/code/module? Why do you need extra type?Haarlem
It's a utility type, the definition of which in the standard library is linked in the answer above.Eulogistic
U
82

How to build a type from enum values in TypeScript?

An enum can hold string and number values.

For strings: you can create a string union from enum values using a template string

enum FooKeys {
  FOO = 'foo',
  BAR = 'bar',
}

type FooValues =`${FooKeys}`; // this equals 'foo' | 'bar'

For numbers: we can either create string union the same way or: starting TS 5.0

TypeScript 5.0 manages to make all enums into union enums by creating a unique type for each computed member. That means that all enums can now be narrowed and have their members referenced as types as well.

Which means:

enum MagicNumbers {
a = 1,
b = 42
}

const numberA : MagicNumbers = 1;
const numberB : MagicNumbers = 2; // will raise an error since TS 5.0

Experiment by yourself

For both: Combining the above, we can build an EnumAsUnion type helper as follows

enum-as-union.ts

type StringValues<T> = {
  [K in keyof T]: T[K] extends string ? T[K] : never;
}[keyof T];

type NumberValues<T> = {
  [K in keyof T]: T[K] extends number ? T[K] : never;
}[keyof T];

/**
 * Usage : type EnumValues = EnumAsUnion<typeof anEnum>
 */
type EnumAsUnion<T> = `${StringValues<T>}` | NumberValues<T>;

Example:

import { EnumAsUnion } from 'enum-as-union';

enum anEnum {
  val1 = 'a',
  val2 = 'b',
  val3 = 1,
  val4 = 2,
}

type EnumValues = EnumAsUnion<typeof anEnum>;

let t: EnumValues;
t = 'a';
t = 'b';
t = 'c'; // error, as expected

t = 1;
t = 2;
t = 3; // error, as expected

Try it here

Uvular answered 13/3, 2023 at 13:15 Comment(1)
really nice and clean, thanksOil
S
9

[@hackape 's solution][1] is great, but I found minimal duplication extending his solution as below:

type ReverseMap<T extends Record<keyof T, any>> = {
  [V in T[keyof T]]: {
    [K in keyof T]: T[K] extends V ? K : never;
  }[keyof T];
}

const Map = {
  'FOO': "foo" as "foo",
  'BAR': "bar" as "bar",
}

const reverseMap: ReverseMap<typeof Map> = Object.entries(Map).reduce((rMap, [k, v]) => {
  rMap[v] = k;
  return rMap;
}, {} as any);

export type Values = keyof typeof reverseMap; // 'foo' | 'bar';

ReverseMap implementation is well explained [here][2]

[1]: https://mcmap.net/q/225702/-typescript-reverse-map-key-value-type-to-value-key [2]: https://mcmap.net/q/225703/-combine-two-types-into-an-interface-elegantly-in-typescript

Update: I found a much simpler solution for ReverseMap

const Obj = {
 FOO: 'foo',
 BAR: 'bar',
} as const;

type ReverseMap<T> = T[keyof T];

export type Values = ReverseMap<typeof Obj>; // 'foo' | 'bar';
Sitter answered 9/12, 2020 at 4:51 Comment(0)
P
6

Does this answer your question?

enum FOO_BAR {
    F = "foo",
    B = "bar",
}

type FooType = Record<FOO_BAR, string>;

const obj: FooType = {
    // you can use enum values, better for refactoring
    [FOO_BAR.F]: "action foo",
    [FOO_BAR.B]: "action bar",

    // or use enum values
    // foo: "action foo",
    // bar: "action bar",
};

obj[FOO_BAR.F]; // -> "action foo"
obj["foo"];     // -> "action foo"

// If you want partial keys
type FooTypePartial = Partial<FooType>;

const objPartial: FooTypePartial = {
    [FOO_BAR.F]: "action foo",
};

objPartial["foo"]; // -> "action foo", may be undefined

Porterfield answered 22/4, 2021 at 12:42 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.