Typescript derive union type from tuple/array values

Viewed 57337

Say I have list const list = ['a', 'b', 'c']

Is it possible to derive from this value union type that is 'a' | 'b' | 'c'?

I want this because I want to define type which allows only values from static array, and also need to enumerate these values at runtime, so I use array.

Example how it can be implemented with an indexed object:

const indexed = {a: null, b: null, c: null}
const list = Object.keys(index)
type NeededUnionType = keyof typeof indexed

I wonder if it is possible to do it without using an indexed map.

5 Answers

Update for TypeScript 3.4:

A new syntax called "const contexts" that will arrive in TypeScript 3.4 will allow for an even simpler solution that doesn't require a function call as demonstrated. This feature is currently in review as seen in this PR.

In short, this syntax allows for the creation of immutable arrays that have a narrow type (i.e. the type ['a', 'b', 'c'] instead of ('a' | 'b' | 'c')[] or string[]). This way, we can create union types from literals as easy as shown below:

const MY_VALUES = <const> ['a', 'b', 'c']
type MyType = typeof MY_VALUES[number]

In alternative syntax:

const MY_VALUES = ['a', 'b', 'c'] as const
type MyType = typeof MY_VALUES[number]

I assume you are living on TypeScript after March 2019 now. (It's November 2021)

I'm just extending the top answer with an exportable utility function:

// Your handmade utils' library file
export type UnionOfArrayElements<ARR_T extends Readonly<unknown[]>> = ARR_T[number];
// Usage
const a = ["hi", "bye", 3, false] as const;
type ta = UnionOfArrayElements<typeof a>; // false | "hi" | "bye" | 3

const b = [4, 5, 6];
type tb = UnionOfArrayElements<typeof b>; // number

If using a an object for storing "constants", this is a way to implement the same idea:

(Note the 'as const' to change the type of keyOne and keyTwo from string to literal.)

const configObj = {
  keyOne: 'literalTypeValueOne' as const,
  keyTwo: 'literalTypeValueTwo' as const,
};

const typeValues = [configObj.keyOne, configObj.keyTwo] as const;
type MyType = typeof typeValues[number];
Related