type A = {
a: {
b: string
c: string
}
x: {
y: number
z: number
}
}
i want to:
type B = {
b: string
c: string
y: number
z: number
}
... implemented through generic type
type B = Unfold<A>
How to do this?
type A = {
a: {
b: string
c: string
}
x: {
y: number
z: number
}
}
i want to:
type B = {
b: string
c: string
y: number
z: number
}
... implemented through generic type
type B = Unfold<A>
How to do this?
First, we need to get the values of every key of A
(or any generic A
-like object):
type GetValues<T extends {[key: string]: any}> = T[keyof T];
This generic let's us pass in an A
-like object and produces a union of all the objects at every key of A
. For demonstration purposes, let's use it to declare a new type Foo
based on A
:
type Foo = GetValues<A>;
If we hover over Foo
we'll see the type it represents - as described above, the union of all the objects at each key of A
.
This is close, but we don't want a union, we want an intersection of all the possible types, per your requirement. Fortunately this post on SO has already solved the issue of converting a union to an intersection.
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never;
Now we have everything we need, we can construct our final generic Unfold<T>
which composes those two bits:
type Unfold<T extends {[key: string]: any}> = UnionToIntersection<GetValues<T>>;
Now we can use Unfold<T>
to convert any A
-like type definition to its "unfolded" version:
type UnfoldedA = Unfold<A>;
const foobar: UnfoldedA = {b: '', c: '', y: 0, z: 0};
I hope smbd will come up with something more readable ))
type A = {
a: {
b: string
c: string
}
x: {
y: number
z: number,
w: {
u: number
}
}
}
type Primitives = string | number | boolean | symbol
/**
* Get all valid nested pathes of object
*/
type AllProps<Obj, Cache extends Array<Primitives> = []> =
Obj extends Primitives ? Cache : {
[Prop in keyof Obj]:
| [...Cache, Prop] // <------ it should be unionized with recursion call
| AllProps<Obj[Prop], [...Cache, Prop]>
}[keyof Obj]
// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
/**
* Iterate through each array of nested keys
* and get last Prop/Value
*/
type Util<Obj, Props extends ReadonlyArray<Primitives>> =
Props extends []
? Obj
: Props extends [infer First]
? First extends keyof Obj
? Obj[First] extends Primitives ? Record<First, Obj[First]> : {}
: never
: Props extends [infer Fst, ...infer Tail]
? Fst extends keyof Obj
? Tail extends string[]
? Util<Obj[Fst], Tail>
: never
: never
: never
type Unfold<T> = UnionToIntersection<Util<A, AllProps<T>>>
type Result = Unfold<A>
Explanation about AllProps
and Util
utils you can find in my blog and