Search code examples
typescripttypescript-genericsmapped-types

TypeScript - remove all properties with particular type


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 Records, is there a way I can recursively remove keys with types of never?


Solution

  • 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.