Ben Southgate
Ben Southgate

Reputation: 3818

Typescript: Retrieve element type information from array type

Say I have some array type T[], is it possible to extract the type T within another alias / interface? For example my (fake) ideal code would be as follows:

// for illustration only...

type ArrayElement<T[]> = T;

// then, ArrayElement<string[]> === string

If no, are there general type theory reasons for not allowing such an operator? If no again, I might suggest it be added.

Upvotes: 173

Views: 172645

Answers (10)

zilijonas
zilijonas

Reputation: 3785

This can be achieved pretty easily by writting index type in brackets:

type ArrayElementType = ArrayType[number];

Update

As a comment below suggests, to create a generic array element type:

type ArrayElementType<ArrayType extends  Array<T>, T = any> = ArrayType[number];

P.S. number is not a value but a type.

Upvotes: 70

Joscha G&#246;tzer
Joscha G&#246;tzer

Reputation: 664

The type-fest library calls this an IterableElement<T> and it allows to extract the element type of any Iterable or AsyncIterable, including arrays and generators:

type MyArray = Array<number>;
type MyArrayElements = IterableElement<MyArray>;

Upvotes: 3

Kelvin praises
Kelvin praises

Reputation: 458

Using conditional types this snippet flattens array types to their element types, or leaves them alone otherwise

type Flatten<T> = T extends any[] ? T[number] : T;

Implementing the snippet above type Str below will have a type of string

// Extracts out the element type.
type Str = Flatten<string[]>;

and type Num below will remain it's original type number

// Leaves the type alone.
type Num = Flatten<number>;

In summary when Flatten is given an array type, it uses an indexed access with number to fetch out string[]’s element type. Otherwise, it just returns the type it was given. This example was sourced from the Typescriptlang handbook on conditional types

Upvotes: 6

doup
doup

Reputation: 911

Based on @will-madem's answer, I've done a couple of tweaks:

  • The generic might be an array, or not: T or T[]
  • It will remove multiple "levels" of array: T[][][] => T

Full example here.

type ArrayElement<MaybeArrayType> = 
  MaybeArrayType extends readonly (infer ElementType)[]
    ? (
        ElementType extends unknown[]
            ? ArrayElement<ElementType>
            : ElementType
    )
    : MaybeArrayType;

Upvotes: 1

yesmeck
yesmeck

Reputation: 316

I got the shortest answer.

type A = Array<string>;
type B = A[0]; // string

https://www.typescriptlang.org/play?ssl=2&ssc=15&pln=1&pc=1#code/C4TwDgpgBAglC8sBOSCGIA8BnYSCWAdgOYB8A3AFCiRQBCCsA2gAwC6ZQA

Upvotes: -4

Damian Kurek
Damian Kurek

Reputation: 180

Another option:

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

You can find more about infer at: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types:

Upvotes: 8

lbesson
lbesson

Reputation: 53

The utility-type library has a type for that https://github.com/piotrwitek/utility-types#valuestypet (along with many others)

For example

import { ValuesType } from 'utility-types';

type NumberArray = number[];
// Expect: number
type NumberItems = ValuesType<NumberArray>;

Upvotes: 5

Will Madden
Will Madden

Reputation: 6955

Update: based on @jerico's answer below

The following type alias will return the type of the elements in an array or tuple:

type ArrayElement<ArrayType extends readonly unknown[]> = 
  ArrayType extends readonly (infer ElementType)[] ? ElementType : never;

So these examples would work:

type A = ArrayElement<string[]>; // string
type B = ArrayElement<readonly string[]>; // string
type C = ArrayElement<[string, number]>; // string | number
type D = ArrayElement<["foo", "bar"]>; // "foo" | "bar"
type E = ArrayElement<(P | (Q | R))[]>; // P | Q | R

type Error1 = ArrayElement<{ name: string }>; 
//                         ^^^^^^^^^^^^^^^^
// Error: Type '{ name: string; }' does not satisfy the constraint 'readonly unknown[]'.

Explanation

The type guard (the bit in the angle brackets) ArrayType extends readonly unknown[] says that we expect the type parameter ArrayType to be at least a readonly array (it also accepts a mutable array) so that we can look at its element type.

This prevents passing in a non-array value, as in the final example, which prevents ArrayElement ever returning never.

Note that readonly unknown[] is syntax added in TypeScript 3.4; for earlier versions use ReadonlyArray<unknown>.

On the right-hand side, the conditional expression asks the compiler to fill in the value of ElementType in the pattern readonly ElementType[] and return ElementType if it can, or never if it can't.

Since the type guard at the beginning means we will only ever be passed a value which matches this pattern, it's guaranteed always to match and never to return never.

Previous answer

type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType[number];

Upvotes: 210

jerico
jerico

Reputation: 1869

Another alternative:

type ArrayElement<A> = A extends readonly (infer T)[] ? T : never

Upvotes: 99

artem
artem

Reputation: 51809

Since 2.1, typescript supports [ ] operator for types. The official name is indexed access types, also called lookup types, and it works like this:

type A = {a: string, b: number} [];

type AElement = A[0];

let e: AElement = {x: 0}; //error TS2322: Type '{ x: number; }' is not 
                       //assignable to type '{ a: string; b: number; }'

Upvotes: 141

Related Questions