TypeScript array to string literal type

ArraysStringTypescriptTypes

Arrays Problem Overview


I currently have both an array of strings and a string literal union type containing the same strings:

const furniture = ['chair', 'table', 'lamp'];
type Furniture = 'chair' | 'table' | 'lamp';

I need both in my application, but I am trying to keep my code DRY. So is there any way to infer one from the other?

I basically want to say something like type Furniture = [any string in furniture array], so there are no duplicate strings.

Arrays Solutions


Solution 1 - Arrays

TypeScript 3.4+

TypeScript version 3.4 has introduced so-called const contexts, which is a way to declare a tuple type as immutable and get the narrow literal type directly (without the need to call a function like shown above).

With this new syntax, we get this nice concise solution:

const furniture = ['chair', 'table', 'lamp'] as const;
type Furniture = typeof furniture[number];

More about the new const contexts is found in this PR as well as in the release notes.

TypeScript 3.0+

With the use of generic rest parameters, there is a way to correctly infer string[] as a literal tuple type and then get the union type of the literals.

It goes like this:

const tuple = <T extends string[]>(...args: T) => args;
const furniture = tuple('chair', 'table', 'lamp');
type Furniture = typeof furniture[number];

More about generic rest parameters

Solution 2 - Arrays

This answer is out of date, see answer above.

The best available workaround:

const furnitureObj = { chair: 1, table: 1, lamp: 1 };
type Furniture = keyof typeof furnitureObj;
const furniture = Object.keys(furnitureObj) as Furniture[];

Ideally we could do this:

const furniture = ['chair', 'table', 'lamp'];
type Furniture = typeof furniture[number];

Unfortunately, today furniture is inferred as string[], which means Furniture is now also a string.

We can enforce the typing as a literal with a manual annotation, but it brings back the duplication:

const furniture = ["chair", "table", "lamp"] as ["chair", "table", "lamp"];
type Furniture = typeof furniture[number];

TypeScript issue #10195 tracks the ability to hint to TypeScript that the list should be inferred as a static tuple and not string[], so maybe in the future this will be possible.

Solution 3 - Arrays

easiest in typescript 3.4: (note TypeScript 3.4 added const assertions)

const furniture = ["chair", "table", "lamp"] as const;
type Furniture = typeof furniture[number]; // "chair" | "table" | "lamp"

also see https://stackoverflow.com/a/55505556/4481226

or if you have these as keys in an object, you can also convert it to a union:

const furniture = {chair:{}, table:{}, lamp:{}} as const;
type Furniture = keyof typeof furniture; // "chair" | "table" | "lamp"

Solution 4 - Arrays

The only adjustement I would suggest is to make the const guaranteed compatible with the type, like this:

type Furniture = 'chair' | 'table' | 'lamp';

const furniture: Furniture[] = ['chair', 'table', 'lamp'];

This will give you a warning should you make a spelling error in the array, or add an unknown item:

// Warning: Type 'unknown' is not assignable to furniture
const furniture: Furniture[] = ['chair', 'table', 'lamp', 'unknown'];

The only case this wouldn't help you with is where the array didn't contain one of the values.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDuncan LukkenaerView Question on Stackoverflow
Solution 1 - ArraysggradnigView Answer on Stackoverflow
Solution 2 - ArraysDenisView Answer on Stackoverflow
Solution 3 - ArraysbristwebView Answer on Stackoverflow
Solution 4 - ArraysFentonView Answer on Stackoverflow