Remove all optional items from a Tuple type
Let say I want to turn a tuple with optional items like [1, 2, 3?, 4?]
to an array containing only the required items -> [1, 2]
What i've come up with as shown below it to turn to never all optional items, and I'm stuck here.
type OnlyReq <L extends any []> = {
[K in keyof L]-?: L [K] extends Required <L> [K] ? L [K] : never
}
type Found = OnlyReq <[1, 2, 3?, 4?]> // [1, 2, never, never]
playground
Solution 1:
My approach here would be to write a recursive conditional type (actually tail recursive so it will work for quite long tuples in TS4.5+) that walks through the tuple until it finds that the rest of it is all optional.
Note that optional elements in tuple types cannot be followed by required elements; that is, something like [1, 2?, 3]
is impossible. So if a tuple has any required elements, the first element in particular must be required.
Here's an implementation:
type OnlyReq<T extends any[], U extends any[] = []> = Partial<T> extends T ? U :
T extends [infer F, ...infer R] ? OnlyReq<R, [...U, F]> : U
We are accumulating the result in the type parameter U
(which starts off as the empty tuple []
), so as soon as we decide to stop iterating we return U
.
The check Partial<T> extends T
uses the Partial<T>
utility type to come up with an all-optional version of the input tuple. Generally speaking T extends Partial<T>
is true but Partial<T> extends T
is not, unless T
is already the same as Partial<T>
... in other words, Partial<T> extends T
if and only if T
is all-optional.
If the tuple T
is all-optional then we return U
. Also if T
is empty we return U
(which is what happens if T
can't be split into a first element F
and a rest tuple R
). If T
has a first element F
, then we know it's required (otherwise T
would be all-optional), and we can just push it onto the end of the U
tuple for the recursive call for the rest of the tuple R
.
Let's see if it works:
type Found = OnlyReq<[1, 2, 3?, 4?]>
// type Found = [1, 2]
Looks good.
Also note that rest elements in tuple types are also considered "optional" in this same test, so they should also be stripped:
type StripRest = OnlyReq<[string, boolean?, ...number[]]>
// type StripRest = [string]
Indeed, a non-tuple array type (Foo[]
) is equivalent to tuple consisting of just a rest element ([...Foo[]]
) and so would be transformed to an empty tuple, which may or may not be what you want:
type Hmm = OnlyReq<number[]>
// type Hmm = []
I'm considering "leading" or "middle" rest elements in tuple types to be out of scope here, since they do weird things and you didn't ask about them (and I hope it doesn't come up because it's annoying to manipulate such types):
type What = OnlyReq<[...string[], number]>
// type What = []
type AlsoWhat = OnlyReq<[string, ...boolean[], number]>
//type AlsoWhat = [string]
Playground link to code