How to get a flattened tuple type of a tuple of tuples?
Asked Answered
P

1

5

Consider I have a tuple of tuples:

type Example = [[3,5,7], [4,9], [0,1,10,9]];

I want to create an utility type Flatten<T> such that Flatten<Example> gives:

type FlatExample = Flatten<Example>;
// type FlatExample = [3,5,7,4,9,0,1,10,9];

For my use case, you can assume the tuple is nested only one level deep. The tuples can have any size.

How can I do this?

Proverbial answered 24/8, 2020 at 19:59 Comment(0)
N
7

To do this you would require recursive conditional types. This will be fully supported in 4.1:

type Example = [[3,5,7], [4,9], [0,1,10,9]];
type Flatten<T extends any[]> = 
    T extends [infer U, ...infer R] ? U extends any[] ? [...U, ... Flatten<R>]: []: []
type FlatExample = Flatten<Example>;

Playground Link

Edit

Simpler version, curtesy of jcalz:

type Example = [[3,5,7], [4,9], [0,1,10,9]];
type Flatten<T extends any[]> = 
    T extends [any, ...infer R] ? [...T[0], ... Flatten<R>]:  []
type FlatExample = Flatten<Example>;

Playground Link

/Edit

You can hack a version even today (the extra indirection is needed to fool the compiler into allowing the recursive conditional type, conceptually this is equivalent to the simpler above version):

type Example = [[3, 5, 7], [4, 9], [0, 1, 10, 9]];
type Flatten<T extends any[]> = T extends [infer U, ...infer R] ? {
    1: U extends any[] ? [...U, ...Flatten<R>] : [],
    2: []
}[U extends any[] ? 1 : 2] : [];

type FlatExample = Flatten<Example>;

Playground Link

Just for fun, the 4.1, generalized flattening version.

type Example = [[3,5,7], [4,9, [10, 12, [10, 12]]], [0,1,10,9, [10, 12]]];
type Flatten<T extends any[]> = 
    T extends [infer U, ...infer R] ? 
        U extends any[] ? 
        [...Flatten<U>, ... Flatten<R>]: [U, ... Flatten<R>]: []
type FlatExample = Flatten<Example>;

Playground Link

Note: While recursive types are more supported in 4.1, you can still run into compiler hardcoded limits, such as type instantiation depth, and total type instances (as such recursive types generate a lot of type instantiations). So use sparingly.

Ned answered 24/8, 2020 at 20:8 Comment(10)
darn, you got here first. a genuine recursive conditional type for 4.1! – Skeptic
@Skeptic I've been hunting for such an answer πŸ˜…. It's an exciting time in the ts type system πŸ˜‰ – Ned
I might have gone with type Flatten<T> = T extends [any, ...infer R] ? [...T[0], ...Flatten<R>] : [] myself but this one also works – Skeptic
@Skeptic added that version as well, you are right it's simpler :) – Ned
Awesome, thank you! What is the minimum TS version for the hack to work? Is it 4.0, due to [...A, ...B]? – Proverbial
@PedroA the version above yes, requires 4.0. You can probably write a helper type to merge the tuples for an equivalent effect .. But I would recommend going with 4.0 if possible. – Ned
@TitianCernicova-Dragomir Ok!! I am already using 4.0 so that's fine, I was asking out of curiosity. For learning purposes, could you please show how to do this helper you said for <4.0? I tried to think of a way for a while (before asking here on SO) but failed... – Proverbial
Hello @Skeptic and @TitanCernicova-Dragomir, actually I went to use this solution now but I am getting "error TS2574: A rest element type must be an array type." on the ...infer R part. I am using TypeScript 4.0.2, what is happening? – Proverbial
Can you provide a minimal reproducible example of that error? Preferably with a link to a web IDE like The TS Playground? Otherwise it's hard to say what's going on – Skeptic
@Skeptic I figured it out. It is an issue with Prettier. It is changing ...infer R into ...(infer R), even in the new 2.1.0 version. I am using a // prettier-ignore comment for now to disable this. Thank you for all the attention! :) – Proverbial

© 2022 - 2024 β€” McMap. All rights reserved.