Steve Holgado

TypeScript: How to get types from arrays

2019TypeScript

TL;DR

const animals = ['cat', 'dog', 'mouse'] as const
type Animal = typeof animals[number]

// type Animal = 'cat' | 'dog' | 'mouse'

Table of contents

The problem

It can be useful to get a type from an array of values so that you can use it as, say, the input to a function to restrict the values that can be passed to it.

Let’s take this simple, although contrived, example:

const animals = ['cat', 'dog', 'mouse']

function getAnimal(name: string) {
  return animals.find(a => a === name)
}

// getAnimal('cat')      // OK
// getAnimal('dog')      // OK
// getAnimal('mouse')    // OK
// getAnimal('elephant') // ?

Should I be able to ask for ‘elephant’?

It would be great to restrict the input to just the values in the array, especially as we know what they are:

const animals = ['cat', 'dog', 'mouse']
type Animal = 'cat' | 'dog' | 'mouse'

function getAnimal(name: Animal) {
  return animals.find(a => a === name)
}

// getAnimal('cat')      // OK
// getAnimal('dog')      // OK
// getAnimal('mouse')    // OK
// getAnimal('elephant') // Type error

Now we get a handy type error so that we know we are passing in the wrong value:

Argument of type ‘“elephant”’ is not assignable to parameter of type ‘Animal’.

Great, but we don’t want to be duplicating all of the values in our array as a type.

Let’s generate a type from our array instead 🚀

Inferred array types

const animals = ['cat', 'dog', 'mouse']

// const animals: string[]

In the above, animals has the inferred type string[] as we have initialised the array with strings.

If we initialised the array with another type(s), say numbers const animals = [5, 10, 20], then TypeScript would infer the type number[], but lets stick to strings for this example.

Const assertions

In order to force the type not to be string[], and instead to be the array of values itself, we can do the following:

const animals = ['cat', 'dog', 'mouse'] as ['cat', 'dog', 'mouse']

// const animals: ['cat', 'dog', 'mouse']

We can now use const assertions (TypeScript 3.4+) to remove the duplication. So, the following is effectively the same as the above:

const animals = ['cat', 'dog', 'mouse'] as const

// const animals: readonly ['cat', 'dog', 'mouse']

Now, animals has the type readonly ['cat', 'dog', 'mouse'].

Getting a type from our array

Now we can get a type from our animals array using typeof:

const animals = ['cat', 'dog', 'mouse'] as const
type Animal = typeof animals[number]

// type Animal = 'cat' | 'dog' | 'mouse'

An array has a numeric index signature, as it’s indexed by numbers (0, 1, 2, …), so we use typeof animals[number] to get a union type of all items in our array: 'cat' | 'dog' | 'mouse'.

If we were to use a specific index, typeof animals[1], we would get a type of just the value at that index: 'dog'.

Arrays of objects

Just in case you’re interested, if we had an array of objects instead of strings:

const animals = [
  { species: 'cat', name: 'Fluffy' },
  { species: 'dog', name: 'Fido' },
  { species: 'mouse', name: 'Trevor' }
] as const

…we would then provide the index signature for the array and the relevant object key:

type Animal = typeof animals[number]['species']

// type Animal = "cat" | "dog" | "mouse"

Hi, I'm a Senior Front-End Developer based in London. Feel free to contact me with questions, opportunities, help with open source projects, or anything else :)

You can find me on GitHub, Stack Overflow or email me directly.