38

What is the best way of making a TypeScript type based on an array of strings? I am on version 2.6.2. The array is long and I do not want to repeat myself by duplicating the string values in an Enum declaration.

What I want to do is something like this:

const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];
export type Color = convertStringArrayToType(colors);

The following solution (source) works fine, but feels hacky:

/** Utility function to create a K:V from a list of strings */
function strEnum<T extends string>(o: Array<T>): {[K in T]: K} {
  return o.reduce((res, key) => {
    res[key] = key;
    return res;
  }, Object.create(null));
}

/**
  * Sample create a string enum
  */

/** Create a K:V */
const Direction = strEnum([
  'North',
  'South',
  'East',
  'West'
])
/** Create a Type */
type Direction = keyof typeof Direction;
Stewart
  • 1,659
  • 4
  • 23
  • 35

2 Answers2

98

Since Typescript 3.4, you could use as const and generate a union type from an array as follows

const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'] as const;
export type Color = typeof colors[number]; // 'red'|'orange'|'yellow'|'green'|'blue'|'indigo'|'violet'
Guido Dizioli
  • 2,007
  • 2
  • 17
  • 29
  • 9
    What is the magic `number` thing which converts it to `'something' | .. ` from `['something']` ? – xaunlopez Jun 03 '21 at 01:31
  • 10
    Thats a good question. Basically you are creating a type `Color` which has the type of the array `colors` accessed at any index. Since `number` can take any numeric value, the type created is a union type of all the possible accessed entries of the array (`'red'|'orange'|'yellow'|'green'|'blue'|'indigo'|'violet'`) – Guido Dizioli Jun 03 '21 at 20:28
1

Building on @guido-dizioli's answer, you can create a reusable function to create a type dynamically and still benefit from IntelliSense:

enter image description here One catch is that the caller needs to define the array with as const:

const properties = [
    "prepare", 
    "loadSession", 
    "proceedBasedOnSession", 
    "fetchConnection"
] as const;

function createObject<T extends readonly string[]>(steps: T): Record<T[number], object> {
    const typed = {} as Record<string, string>;
    steps.forEach(step => typed[step] = step);
    return typed as unknown as Record<T[number], object>;
}
Sameera
  • 1,025
  • 13
  • 36