-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spread / Flatten Types #31192
Comments
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:
Proceed with caution, I guess. |
That 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... |
@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. |
Essentially the spread/flatten operator would take an object and for each property apply |
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. |
Thanks. Your code above led me to It would be nice if it were possible with the |
@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:
|
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...T
which 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:
The text was updated successfully, but these errors were encountered: