HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptCritical

Derive union type from tuple/array values

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
tupleunionderivefromtypevaluesarray

Problem

Say I have an array:

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


Is it possible to do it without using an indexed map?

Solution

CURRENT ANSWER

In TypeScript 3.4 and above, you can use a const assertion to tell the compiler to retain the specific literal types of any literal values in an expression.

const list = ['a', 'b', 'c'] as const; // const assertion
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c';


Playground link to code
UPDATE Feb 2019

In TypeScript 3.4, which should be released in March 2019 it will be possible to tell the compiler to infer the type of a tuple of literals as a tuple of literals, instead of as, say, string[], by using the as const syntax. This type of assertion causes the compiler to infer the narrowest type possible for a value, including making everything readonly. It should look like this:

const list = ['a', 'b', 'c'] as const; // TS3.4 syntax
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c';


This will obviate the need for a helper function of any kind. Good luck again to all!

UPDATE July 2018

It looks like, starting with TypeScript 3.0, it will be possible for TypeScript to automatically infer tuple types. Once is released, the tuple() function you need can be succinctly written as:

export type Lit = string | number | boolean | undefined | null | void | {};
export const tuple = (...args: T) => args;


And then you can use it like this:

const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'


Hope that works for people!

UPDATE December 2017

Since I posted this answer, I found a way to infer tuple types if you're willing to add a function to your library. Check out the function tuple() in tuple.ts. Using it, you are able to write the following and not repeat yourself:

const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'


Good luck!

ORIGINAL July 2017

One problem is the literal ['a','b','c'] will be inferred as type string[], so the type system will forget about the specific values. You can force the type system to remember each value as a literal string:

const list = ['a' as 'a','b' as 'b','c' as 'c']; // infers as ('a'|'b'|'c')[]


Or, maybe better, interpret the list as a tuple type:

const list: ['a','b','c'] = ['a','b','c']; // tuple


This is annoying repetition, but at least it doesn't introduce an extraneous object at runtime.

Now you can get your union like this:

type NeededUnionType = typeof list[number];  // 'a'|'b'|'c'.

Code Snippets

const list = ['a', 'b', 'c'] as const; // const assertion
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c';
const list = ['a', 'b', 'c'] as const; // TS3.4 syntax
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c';
export type Lit = string | number | boolean | undefined | null | void | {};
export const tuple = <T extends Lit[]>(...args: T) => args;
const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'
const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'

Context

Stack Overflow Q#45251664, score: 539

Revisions (0)

No revisions yet.