Is it possible to write a mapped type using a product of nested keys to generate keys?
This is what I want to obtain:
type Nested = {
foo: string;
bar: { baz: number; };
squick: { squeck: string; squawk: boolean; };
garble: { gobble: string; };
};
type Unnested = Flatten<Nested, 'bar' | 'squick'>;
// Should be equivalent to:
type Unnested = {
foo: string;
'bar.baz': number;
'squick.squeck': string;
'squick.squawk': boolean;
garble: { gobble: string; };
};
I know I can do this:
type Flatten<T, K extends string & keyof T> = Omit<T, K> & {
[k in K as `${k}.${string & keyof T[k]}`]: T[k][string & keyof T[k]];
};
But then each resulting mapped property's type is a union of all the possible property types in the nested object (i.e. in the previous example both squick.squeck
and squick.squawk
have type string | boolean
).
What I actually want is that each resulting property preserves its original type.
Actually, this solution can't even restrict the inner properties to the children of the outer properties from which they descend (i.e. it will also generate squick.baz
, bar.squeck
and bar.squawk
).
My intuition is that, for each outer property, I need to "loop" on each inner property, thus creating a product.
Is there a TypeScript notation that will allow me to achieve this?
type Flatten<T extends object> = {
[K in keyof T]-?: (
x: NonNullable<T[K]> extends infer V
? V extends object
? V extends readonly any[]
? Pick<T, K>
: Flatten<V> extends infer FV
? {
[P in keyof FV as `${Extract<K, string | number>}.${Extract<P, string | number>}`]: FV[P]
}
: never
: Pick<T, K>
: never
) => void
} extends Record<keyof T, (y: infer O) => void>
? O extends unknown
? { [K in keyof O]: O[K] }
: never
: never