I have a type
which is used to project keys present in T
to the common keys and the types thereof present in U
:
type IntersectByCommonKey<T, U, V = any> = {
[K in keyof U]: K extends keyof T ?
T[K] extends V ?
U[K]
: T[K] extends object ?
IntersectByCommonKey<T[K], U[K], V>
: T[K] extends object | undefined ?
IntersectByCommonKey<T[K], U[K], V> | undefined
: never
: never
};
IntersectByCommonKey Playground Link
where for each key K
in U
, if T
has the same key K
, and if the type at K
extends V
, includes the shape for that key, otherwise never
.
With the type returned by IntersectByCommonKey
I want to remove every key that has a never
type which means it must cope with nested Record
shapes too.
It's easy enough to remove top-level keys:
type ExcludeKeysWithTypeOf<T, V> = {
[K in keyof T]: Exclude<T[K], undefined> extends V ? never : K
}[keyof T]
type Without<T, V> = Pick<T, ExcludeKeysWithTypeOf<T, V>>;
ExcludeKeysWithTypeOf Playground Link
However, I'm struggling to achieve the same result for nested Record
s, is there a way I can recursively remove keys with types of never
?
We need to make recursive call to go into nested records types. Here an example solution:
type Without<T, V, WithNevers = {
[K in keyof T]: Exclude<T[K], undefined> extends V ? never
: (T[K] extends Record<string, unknown> ? Without<T[K], V> : T[K])
}> = Pick<WithNevers, {
[K in keyof WithNevers]: WithNevers[K] extends never ? never : K
}[keyof WithNevers]>
The core idea is when our T[K]
will have type of Record, it will recursively call again itself (Without
type level function). The whole type will reduce to wanted form in which every nested object will have removed keys we wanted.
The whole code snippet - the playground
Note: I moved calculating of WithNevers
inside arguments list of the type in order to achieve better readability. We can also define it as separate type.