Filter tuple type in TypeScript
1type FilterOut<T extends any[], Filter> = any; // implementation23type cases = [4 Expect<Equal<FilterOut<[1, never, "a"], never>, [1, "a"]>>,5 Expect<Equal<FilterOut<[1, never, "a"], "a">, [1]>>6];
Today we discuss TupleFilter
Let's try it out 🚀
Delegate a subtask
Let's start the solution from iteration over the tuple using recursive conditional types.
1type FilterOut<T extends any[], F> = T extends [infer Head, ...infer Tail]2 ? [Head, ...FilterOut<Tail, F>]3 : [];
This way we don't really filter anything. Let's delegate the filtering part to FilterElement
.
1type FilterElement<Element, F> = [Element];23type FilterOut<T extends any[], F> = T extends [infer Head, ...infer Tail]4 ? [...FilterElement<Head, F>, ...FilterOut<Tail, F>]5 : [];
Let's save the progress in Playground – https://tsplay.dev/Wkjx2m
As you see, this way we don't really filter anything yet as we don't use a filter F
in FilterElement
Filter the current element
To be able to filter the element, let's use a filter F
:
1type FilterElement<Element, F> = Element extends F ? [] : [Element];23type FilterOut<T extends any[], F> = T extends [infer Head, ...infer Tail]4 ? [...FilterElement<Head, F>, ...FilterOut<Tail, F>]5 : [];
There's a thought to write it using this conditional type Element extends F
but that's incorrect
To be able to test incorrect behaviour, let's have a look at the current implementation in Playground – https://tsplay.dev/W4pLeW
We will see broken cases, let's focus on the first one.
1type BrokenFilterOut = FilterOut<[never], never>; // never2type BrokenFilterElement = FilterElement<never, never>; // never3type Test2 = [...never]; // never
It happens because we used not just usual conditional type but instead distributed conditional type like T extends unknown
or T extends never
:
1type DistributedConditionalTypeWithNever<T> = T extends never ? never : [T];2type DistributedConditionalTypeWithUnknown<T> = T extends unknown ? [T] : never;34type Test1 = DistributedConditionalTypeWithNever<never>; // never5type Test2 = DistributedConditionalTypeWithUnknown<never>; // never6type Test3 = DistributedConditionalTypeWithNever<1>; // [1]7type Test4 = DistributedConditionalTypeWithUnknown<1>; // [1]
For never
element with a filter never
we get never
which is later put into spread which leads to never
To be able to filter it correctly, let's try to check the equality of the types with the expression [T] extends [U]
:
1type FilterElement<Element, F> = [Element] extends [F] ? [] : [Element];23type FilterOut<T extends any[], F> = T extends [infer Head, ...infer Tail]4 ? [...FilterElement<Head, F>, ...FilterOut<Tail, F>]5 : [];
First of all it's working for never
as [never] extends [never]
goes to "then" branch. See:
1type IsNever<T> = [T] extends [never] ? true : false;23type Check1 = IsNever<never>; // true4type Check2 = IsNever<1>; // false5type Check3 = IsNever<false>; // false6type Check4 = IsNever<"1">; // false
Second, it's working fine if the filter is a union type like 'a' | 'b'
:
1type HasAOrB<T> = [T] extends ["a" | "b"] ? true : false;23type Check1 = HasAOrB<never>; // true4type Check2 = HasAOrB<"a">; // true5type Check3 = HasAOrB<"b">; // true6type Check4 = HasAOrB<"a" | "b">; // true7type Check5 = HasAOrB<undefined>; // false8type Check6 = HasAOrB<null>; // false9type Check7 = HasAOrB<"c">; // false
Solution
So the final solution looks like this
1type FilterElement<Element, F> = [Element] extends [F] ? [] : [Element];23type FilterOut<T extends any[], F> = T extends [infer Head, ...infer Tail]4 ? [...FilterElement<Head, F>, ...FilterOut<Tail, F>]5 : [];
Let's sum up the whole solution:
- We iterate over the tuple using conditional type –
T extends [infer Head, ...infer Tail]
- We delegate filtering the current element to the type
FilterElement
- We check if the current element is included in the filter with the conditional type
[Element] extends [F]
- If element is filtered, we return empty tuple and then it's not added into the result
To be able to check it with tests, please have a look at the Playground – https://tsplay.dev/Wy58dW
Thank you for your time and have a wonderful evening 🌇
typescript