TypeScript array to string literal type
Solution 1
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
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
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"
Duncan Lukkenaer
Updated on July 08, 2022Comments
-
Duncan Lukkenaer almost 2 years
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. -
robC almost 5 yearsCan I ask, what's the purpose of the index signature annotation
[number]
? Is that not inferred? -
Daniel Dror almost 5 years@ggradnig Thanks for the answer! this feels like it was meant to take the place of string enums with reverse mappings, or am I wrong? are there other use cases for this?
-
Jason Kohles about 4 yearsThe reason for the
[number]
is that without ittypeof furniture
would return an array type. With the index signaturetypeof furniture[number]
is saying "the type of any valid numeric index in furniture, so you get a type that is a union of the values instead of an array type. -
Slavik Meltser almost 4 yearsUnfortunately, this only works with literal arrays. This will not work:
const a = ["a", "b", "c"]; const b = a as const;
- This will throw the following error:A 'const' assertions can only be applied to references to enum members, or string, number, boolean, array, or object literals.
-
Ariart about 3 yearsThis is an awesome solution to get PHPStorm autocompletion for huge string possibles values for a string parameter. Do you now if there is a way to document each const value to get hints in addition to auto-completion (in jsdoc style) ?
-
Marco Mesen over 2 yearsThe best solution I have never seen, thanks! :)
-
Erdős-Bacon over 2 years@SlavikMeltser: I think the issue is that your first definition of the array must have the
as const
on it. In your example, a new instruction could be inserted between defining thea
and definingb
that changesa
, so I believe that's why TS doesn't want to trust it. For me, running TS 4.5.2, the following works without issue:const a = ["x", "y", "z"] as const;
//const b = a;
If I then look at the TS type ofb
, I getconst b: readonly ["x", "y", "z"]
. -
ggradnig about 2 years@SlavikMeltser @Erdős-Bacon Typescript has implemented rules to infer the correct type of an value if no type annotation is given. For example,
["a", "b", "c"]
is inferred asstring[]
. This is just a choice that was made by the TypeScript designers - it could as well beconst ["a", "b", "c"]
. The reason for that choice is that most of the times,string[]
is what the developer wants, which is why you need to overrule it by usingas const
if you want to have it another way. -
ggradnig about 2 years@SlavikMeltser and yes, a variable typed
string[]
cannot be assertedas const
after it's declaration. -
ggradnig about 2 years@Ariart arrording to github.com/microsoft/TypeScript/issues/30445, you can use
const arr2 = /** @type {const} */ ([1, 2, 3])
to document the value correctly in JSDoc