TypeScript array to string literal type

42,982

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"
Share:
42,982
Duncan Lukkenaer
Author by

Duncan Lukkenaer

Updated on July 08, 2022

Comments

  • Duncan Lukkenaer
    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
    robC almost 5 years
    Can I ask, what's the purpose of the index signature annotation [number]? Is that not inferred?
  • Daniel Dror
    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
    Jason Kohles about 4 years
    The reason for the [number] is that without it typeof furniture would return an array type. With the index signature typeof 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
    Slavik Meltser almost 4 years
    Unfortunately, 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
    Ariart about 3 years
    This 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
    Marco Mesen over 2 years
    The best solution I have never seen, thanks! :)
  • Erdős-Bacon
    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 the a and defining b that changes a, 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 of b, I get const b: readonly ["x", "y", "z"].
  • ggradnig
    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 as string[]. This is just a choice that was made by the TypeScript designers - it could as well be const ["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 using as const if you want to have it another way.
  • ggradnig
    ggradnig about 2 years
    @SlavikMeltser and yes, a variable typed string[] cannot be asserted as const after it's declaration.
  • ggradnig
    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