28

I have tuple of Maybe types:

class Maybe<T>{ }

type MaybeTuple = [Maybe<string>, Maybe<number>, Maybe<boolean>];

and I want to turn this into a tuple of types:

type TupleIWant = [string, number, boolean];

so I tried this:

type ExtractTypes<T> = T extends Maybe<infer MaybeTypes>[] ? MaybeTypes : never;

type TypesArray = ExtractTypes<MaybeTuple>; // string | number | boolean NOT [string, number, boolean]

Which doesn't work :-(

I get (string | number | boolean)[] rather than the tuple I want: [string, number, boolean]

Is what I want to do currently possible?

Roaders
  • 4,373
  • 8
  • 50
  • 71

2 Answers2

28

You'll need to use a mapped tuple type, which is supported in TypeScript 3.1.

You can make a mapped type that has properties 0, 1, 2 and length of the correct types, like this:

class Maybe<T> {}

type MaybeTuple = [Maybe<string>, Maybe<number>, Maybe<boolean>];

type MaybeType<T> = T extends Maybe<infer MaybeType> ? MaybeType : never;
type MaybeTypes<Tuple extends [...any[]]> = {
  [Index in keyof Tuple]: MaybeType<Tuple[Index]>;
} & {length: Tuple['length']};

let extractedTypes: MaybeTypes<MaybeTuple> = ['hello', 3, true];

If you're using an older version of typescript, you can use an unreleased version of TypeScript, or as a workaround, you can write a conditional type to match against tuples as long as you think you are likely to have, e.g., like this.

Marouane Fazouane
  • 1,279
  • 14
  • 20
Matt McCutchen
  • 28,856
  • 2
  • 68
  • 75
0

You need to exclude the non-index keys of the array, or you'll also be mapping over push, length, etc:

type NumStrDate = [number, string, Date]
type IdxOf<T extends any[]> = Exclude<keyof T, keyof any[]>
type OrUndef<T extends any[]> = {[K in IdxOf<T>]: T[K] | undefined}
type MaybeNSD = OrUndef<NumStrDate>

const fine: MaybeNSD = [5, 'foo', undefined]
const problem: MaybeNSD = ['foo', undefined, undefined]

If you hover on MaybeNSD it will show you the resolved meaning:

type MaybeNSD = {
    0: number | undefined;
    1: string | undefined;
    2: Date | undefined;
}

playground link

Luke Miles
  • 941
  • 9
  • 19
  • 2
    This isn't true, sorry. If you exclude the non-index keys of the array in this manner, then your output type will not be a tuple. There are use cases where it doesn't make a difference, but the question here is specifically about mapping a tuple to a tuple, so this answer doesn't seem to apply. – jcalz May 31 '22 at 21:39