Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning a nested array type in Typescript

Tags:

typescript

This question is much more academic than practical.

I have a function filledArray(val, ...dims), which takes a fill value and a variable number of dimensions, and builds a nested multidimensional array filled with that value, to the specification in the dimensions. For example:

> filledArray('x', 5)
[ 'x', 'x', 'x', 'x', 'x' ]

> filledArray('x', 2, 5)
[ [ 'x', 'x', 'x', 'x', 'x' ],
  [ 'x', 'x', 'x', 'x', 'x' ], ]

> filledArray('x', 2, 5, 1)
[ [ [ 'x' ], [ 'x' ], [ 'x' ], [ 'x' ], [ 'x' ] ],
  [ [ 'x' ], [ 'x' ], [ 'x' ], [ 'x' ], [ 'x' ] ] ]

Typescript infers that the return type of this function is any. Can the true return type be expressed in the type system somehow, so that the three return values above are correctly typed as string[], string[][], and string[][][]? (And so on, for deeper and deeper arrays?)

A sample implementation of this function:

function filledArray<T>(val: T, ...dims: number[]) {
    if (dims.length == 0)
        return val

    let [dim, ...rest] = dims
    return Array.from(Array(dim), _ => filledArray(val, ...rest))
}

Interestingly, it even seems like specifying a fixed number of overload declarations seems to make the final ...rest in the function somehow be of the wrong type...

like image 553
Joppy Avatar asked Mar 28 '26 18:03

Joppy


1 Answers

Such a return type can be expressed with a recursive conditional type.

type FilledArray<N extends number[], T> = 
  N extends [number, ...infer Rest extends number[]]
    ? FilledArray<Rest, T>[]
    : T

function filledArray<T, N extends number[]>(val: T, ...dims: N): FilledArray<N, T> {
  if (dims.length == 0)
    return val as unknown as FilledArray<N, T>

  let [dim, ...rest] = dims
  return Array.from(Array(dim), _ => filledArray(val, ...rest)) as FilledArray<N, T>
}

We can recursively check how many numbers the dims tuple holds and add a [] to the return type for each number.

The first conditional N extends [number, ...infer Rest extends number[]] checks if there is still at least one element in the tuple N and simultaneously stores every element except the first one in the inferred type Rest. Rest might also be an empty tuple if there are no more elements. If the conditional is true, we call the type recursively with Rest and add a [] to the end of it. If the conditional is false, we can return the type T to end the recursion.

Since TypeScript does not understand how conditional return types relate to the function implementation, we have to use type assertions for the returned values.

Here are some tests:

const a = filledArray('x', 5)
// const a: "x"[]

const b = filledArray('x', 2, 5)
// const b: "x"[][]

const c = filledArray(0, 2, 5, 1)
// const c: 0[][][]

Playground

like image 168
Tobias S. Avatar answered Apr 01 '26 10:04

Tobias S.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!