Repository navigation
Spread / Flatten Types #31192
Description
Activity
This isn't precisely what you're asking for, but it's as close as I could come:
type ObjKeyof<T> = T extends object ? keyof T : never type KeyofKeyof<T> = ObjKeyof<T> | { [K in keyof T]: ObjKeyof<T[K]> }[keyof T] type StripNever<T> = Pick<T, { [K in keyof T]: [T[K]] extends [never] ? never : K }[keyof T]>; type Lookup<T, K> = T extends any ? K extends keyof T ? T[K] : never : never type SimpleFlatten<T> = T extends object ? StripNever<{ [K in KeyofKeyof<T>]: Exclude<K extends keyof T ? T[K] : never, object> | { [P in keyof T]: Lookup<T[P], K> }[keyof T] }> : T type NestedFlatten<T> = SimpleFlatten<SimpleFlatten< SimpleFlatten<SimpleFlatten<SimpleFlatten<T>>>>>; interface SimpleFlattenTest { a: { b: string, c: number }, meow: { b: number, d: boolean } } type S1 = SimpleFlatten<SimpleFlattenTest>; // { b: string | number, c: number, d: boolean } type S2 = SimpleFlatten<{}>; // {} // differs here: type S3 = SimpleFlatten<boolean>; // boolean, not never // differs here: type S4 = SimpleFlatten<{ a: string }>; // {a: string}, not never interface NestedFlattenTest { a: { b: string, c: number }, meow: { b: number, d: boolean }, nested: { ignored: { d: string }, taco: { c: string } } } type N1 = NestedFlatten<NestedFlattenTest>; // { // b: string | number, c: number | string, d: boolean | string } type N2 = NestedFlatten<{}>; // {} type N3 = NestedFlatten<boolean>; // boolean type N4 = NestedFlatten<{ a: string }>; // {a: string}
There are big caveats:
- The possible edge cases boggle my mind, so who knows what will happen if you poke at this with recursive/optional/other properties or types.
- The "nested" case is not truly nested but only goes down five or so levels. Recursive/circular types where the recursion is not hidden by property accesses are not supported. There have been tricks that fool the compiler into trying to evaluate them (mostly by pretending that the type is one of these supported tree-like types and then indexing into them) but they scare me and are not recommended.
- Even the five or so levels of this stuff involves a lot of compiler processing. It's a bunch of work to drill down into two layers of objects and pull everything from level 2 up into level 1 in the appropriate way. And the extra work to keep applying is probably exponential in the depth of the tree (at least the way I've got it implemented above), so I'd stay away from it even though it's a finite depth.
Proceed with caution, I guess.
Reacted by Titian Cernicova-Dragomir, Philip Diffenderfer, Ran Lottem, Kevin Cui, Corentin Girard, Béré Cyriac, Mostafa Rahmati and Sayan DasClickerMonkey commented
on May 2, 2019 AuthorMore actionsThat solves my particular problem... I'll be careful and hope my users share the same precautions.
Just when you think you understand how to Typescript...
Reacted by Ran Lottem and Al KClickerMonkey commented
on May 2, 2019 AuthorMore actionsJoe Calzaretta (@jcalz) I've ran into another problem, and your solution above doesn't really address. Maybe I'm missing something? If I'm not, maybe there is some utility in this feature suggestion.
I have the following type:
type Funcs = { a: () => void; v: (text: string) => number; }
And if I flatten/spread it, I would expect the following:
type FlattenOutput = { () => void; (text: string) => number; };
Any pointers would be greatly appreciated, I understand about 80% of your types above... so if this sort of flattening is possible like above I'm failing at figuring it out.
Reacted by Joe Calzaretta and Julian FrumarClickerMonkey commented
on May 2, 2019 AuthorMore actionsEssentially the spread/flatten operator would take an object and for each property apply
&to each one.That is a different operation from what you were describing before, but here:
type IntersectProperties<T> = T extends object ? { [K in keyof T]: (arg: T[K]) => void } extends Record< keyof T, (arg: infer A) => void > ? A : never : T; type Funcs = { a: () => void; v: (text: string) => number; }; type IPFuncs = IntersectProperties<Funcs>; // type IPFuncs = (() => void) & ((text: string) => number) 👍 type Foo = { a: string; b: number }; type Bar = IntersectProperties<Foo>; // type Bar = string & number 😕
Note how that does bizarre things to non-function properties, so you'd have to combine this with the other type functions somehow.
I am deeply skeptical that you could actually implement a function that turns a general object with function-valued properties into a single overloaded function, though. How do you select which function to call at runtime?
declare function magic<F extends Record<keyof F, Function>>( funcs: F ): IntersectProperties<F>; // how do you implement this interface Funky { a: (a: string) => number; b: (b: number) => string; } const funky: Funky = { a: s => s.length, b: n => n + "!" }; const hmm = magic(funky); hmm(1); // "1!" hmm("a"); // 1
You'd probably have to pass argument type guard functions along with your function object in order for that to work. So, uh, yeah.
Anyway, I don't think this discussion is best had here, since you'd first want to demonstrate that the feature you want is not implementable without a change to the language (and that your use case is compelling enough that it's worth doing). Questions about how to implement particular type functions probably belong on Stack Overflow.
Reacted by Al K and JoeClickerMonkey commented
on May 2, 2019 AuthorMore actionsThanks.
Your code above led me to
type Intersect<T> = T extends { [K in keyof T]: infer E } ? E : T;which essentially does the same thing as I wanted, except with the|operator.It would be nice if it were possible with the
&operator at some point, but your solutions address my immediate need.Reacted by Al KJoe Calzaretta (@jcalz) your code for IntersectProperties above is exactly what I was looking for, so thank you!
I think this should totally be on the "Advanced Types" section of the guide (along with the Intersect above too as a stepping stone, but with a different name).
I say that not only because of its value in utility, but also because of its instructional value if it also comes with:
- an explanation of how it works
- the mindset involved for constructing it out of its building blocks
- locked as resolved and limited conversation to collaborators
on Oct 21, 2025
Search Terms
Suggestion
Allow known nested object types to be spread & merged.
If you have a type that meets the condition (or something similar)
T extends { [P in keyof T]: object }I want to be able to flatten it like so...Twhich merges all sub-object types into one type.Use Cases
What do you want to use this for?
To create better types for popular libraries (namely Vuex at the moment). This would also help make typesafe many utility functions which perform this same function of flattening objects.
What shortcomings exist with current approaches?
I don't think it's possible at the moment.
Examples
Checklist
My suggestion meets these guidelines: