Here's a solution using mapped and conditional types: FlattenPairs
constructs a union of pairs like ['name', string] | ['lastName', string] | ['age', number]
, then Flatten
converts this into an object type. I assumed that the nested properties are not necessarily all named nested
(and do not necessarily have predictable property names), so the type Primitive
is needed as the base case for the recursion.
type Primitive = string | number | boolean
type FlattenPairs<T> = {[K in keyof T]: T[K] extends Primitive ? [K, T[K]] : FlattenPairs<T[K]>}[keyof T] & [PropertyKey, Primitive]
type Flatten<T> = {[P in FlattenPairs<T> as P[0]]: P[1]}
Example:
interface TestInterface {
name: string
nested: {
lastName: string
nested: {
age: number
}
}
}
// {age: number, lastName: string, name: string}
type Test = Flatten<TestInterface>
Playground Link
The helper type FlattenPairs
can be written in a slightly simpler way if the nested property name(s) are known:
type FlattenPairs<T> = {[K in keyof T]: K extends 'nested' ? FlattenPairs<T[K]> : [K, T[K]]}[keyof T]
Playground Link