Typescript: Remove entries from tuple type
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.
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
Supplementary answer:
An extension of the approach in ford04's answer allows us to create a "splicer" utility type that can remove values at arbitrary indices (in case someone finds this in search of a solution to type-safe splice
).
This involves creating a utility type that will generate the tuple with undefined
from a given tuple and index:
type UndefIndex<T extends any[], I extends number> = {
[ P in keyof T ] : P extends Exclude<keyof T, keyof any[]> ? P extends `${I}` ? undefined : T[P] : T[P]
}
Then this is just a matter of composing the UndefIndex
and FilterUndefined
types:
type FilterUndefined<T extends any[]> = T extends [] ? [] :
T extends [infer H, ...infer R] ?
H extends undefined ? FilterUndefined<R> : [H, ...FilterUndefined<R>] : T;
type SpliceTuple<T extends any[], I extends number> = FilterUndefined<UndefIndex<T, I>>;
type a = SpliceTuple<[1,2,3], 0>; //[2,3]
type b = SpliceTuple<[1,2,3], 1>; //[1,3]
type c = SpliceTuple<[1,2,3], 2>; //[1,2]
type d = SpliceTuple<[1,2,3], 3>; //[1,2,3]
Playground