Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: Remove entries from tuple type

not sure if this is possible, but I would like to be able to define a type that converts tuples like: [number, string, undefined, number] to [number, string, number] (ie filter out undefined).

I thought about something like this:

type FilterUndefined<T extends any[]> = {
    [i in keyof T]: T[i] extends undefined ? /* nothing? */ : T[i];
}

Sadly I am am pretty sure that there is no way to achieve this.

like image 628
Jan van Brügge Avatar asked Feb 09 '19 14:02

Jan van Brügge


2 Answers

TS 4.1

Filter operations on tuples are now officially possible:

type FilterUndefined<T extends unknown[]> = T extends [] ? [] :
    T extends [infer H, ...infer R] ?
    H extends undefined ? FilterUndefined<R> : [H, ...FilterUndefined<R>] : T
Let's do some tests to check, that it is working as intended:
type T1 = FilterUndefined<[number, string, undefined, number]> 
// [number, string, number]
type T2 = FilterUndefined<[1, undefined, 2]> // [1, 2]
type T3 = FilterUndefined<[undefined, 2]> // [2]
type T4 = FilterUndefined<[2, undefined]> // [2]
type T5 = FilterUndefined<[undefined, undefined, 2]> // [2]
type T6 = FilterUndefined<[undefined]> // []
type T7 = FilterUndefined<[]> // []

More infos

  • Recursive conditional types #40002 (TS 4.1)
  • Variadic tuple types #39094 (TS 4.0)

Playground

like image 33
ford04 Avatar answered Sep 21 '22 02:09

ford04


Got it! But it need a lot of recursive magic:

type PrependTuple<A, T extends Array<any>> =
  A extends undefined ? T : 
  (((a: A, ...b: T) => void) extends (...a: infer I) => void ? I : [])

type RemoveFirstFromTuple<T extends any[]> = 
  T['length'] extends 0 ? undefined :
  (((...b: T) => void) extends (a, ...b: infer I) => void ? I : [])

type FirstFromTuple<T extends any[]> =
  T['length'] extends 0 ? undefined : T[0]

type NumberToTuple<N extends number, L extends Array<any> = []> = {
  true: L;
  false: NumberToTuple<N, PrependTuple<1, L>>;
}[L['length'] extends N ? "true" : "false"];

type Decrease<I extends number> = RemoveFirstFromTuple<NumberToTuple<I>>['length']
type H = Decrease<4>

type Iter<N extends number, Items extends any[], L extends Array<any> = []> = {
  true: L;
  false: Iter<FirstFromTuple<Items> extends undefined ? Decrease<N> : N, RemoveFirstFromTuple<Items>, PrependTuple<FirstFromTuple<Items>, L>>;
}[L["length"] extends N ? "true" : "false"];

type FilterUndefined<T extends any[]> = Iter<T['length'], T>
type I = [number, string, undefined, number];
type R = FilterUndefined<I>


Playground

How it works:

PrependToTuple is util that takes item A and list T and add it on first place when A is not undefined. PrependToTuple<undefined, []> => [], PrependToTuple<undefined, [number]> => [number]

RemoveFirstFromTuple works pretty mach i the same way

NumberToTuple is recursively check if length of final Tuple is N, if not he add 1 to recursive call. This util is needed to create Decrease util.

And the most important z Iter works like recursive loop, when length of final tuple is N (size of Input) its return Output, but PrependToTuple is not increasing length when we try do add undefined, so when Iter<FirstFromTuple<Items> extends undefined we have to decrease N.

like image 54
Przemyslaw Jan Beigert Avatar answered Sep 23 '22 02:09

Przemyslaw Jan Beigert