Skip to content

Instantly share code, notes, and snippets.

@ahuggins-nhs
Last active May 4, 2023 16:41
Show Gist options
  • Save ahuggins-nhs/826906a58e4c1e59306bc0792e7826d1 to your computer and use it in GitHub Desktop.
Save ahuggins-nhs/826906a58e4c1e59306bc0792e7826d1 to your computer and use it in GitHub Desktop.
Deeply omit members of an interface or type
// Taken from https://stackoverflow.com/questions/55539387/deep-omit-with-typescript
/** Union of primitives to skip with deep omit utilities. */
type Primitive = string | Function | number | boolean | Symbol | undefined | null
/** Deeply omit members of an array of interface or array of type. */
export type DeepOmitArray<T extends any[], K> = {
[P in keyof T]: DeepOmit<T[P], K>
}
/** Deeply omit members of an interface or type. */
export type DeepOmit<T, K> = T extends Primitive ? T : {
[P in Exclude<keyof T, K>]: //extra level of indirection needed to trigger homomorhic behavior
T[P] extends infer TP ? // distribute over unions
TP extends Primitive ? TP : // leave primitives and functions alone
TP extends any[] ? DeepOmitArray<TP, K> : // Array special handling
DeepOmit<TP, K>
: never
}
/** Deeply omit members of an array of interface or array of type, making all members optional. */
export type PartialDeepOmitArray<T extends any[], K> = Partial<{
[P in Partial<keyof T>]: Partial<PartialDeepOmit<T[P], K>>
}>
/** Deeply omit members of an interface or type, making all members optional. */
export type PartialDeepOmit<T, K> = T extends Primitive ? T : Partial<{
[P in Exclude<keyof T, K>]: //extra level of indirection needed to trigger homomorhic behavior
T[P] extends infer TP ? // distribute over unions
TP extends Primitive ? TP : // leave primitives and functions alone
TP extends any[] ? PartialDeepOmitArray<TP, K> : // Array special handling
Partial<PartialDeepOmit<TP, K>>
: never
}>
@mikecann
Copy link

@armenr its actually not trhats bad really, it looks scary but its just a bunch of ternay operators over types (basically just if-thens in type code)

@simon-abbott
Copy link

This can be simplified a bit:

/** Union of primitives to skip with deep omit utilities. */
type Primitive = string | Function | number | boolean | symbol | undefined | null;

/** Deeply omit members of an interface or type. */
type DeepOmit<T, K extends keyof any> = T extends Primitive
  ? T
  : T extends any[]
  ? { [P in keyof T]: DeepOmit<T[P], K> }
  : {
      [P in Exclude<keyof T, K>]: T[P] extends infer TP // Distribute over unions
        ? DeepOmit<TP, K>
        : never;
    };

@theoinglis
Copy link

Thanks, this is really useful when removing __typename from apollo graphql! I had an issue with optionals though so I extended it as follows:

/** Union of primitives to skip with deep omit utilities. */
type Primitive =
  | string
  | Function
  | number
  | boolean
  | Symbol
  | undefined
  | null;

/** Key Retrieval */

export type KeysOfType<T, U> = {
  [K in keyof T]: T[K] extends U ? K : never;
}[keyof T];

export type RequiredKeys<T> = Exclude<
  KeysOfType<T, Exclude<T[keyof T], undefined>>,
  undefined
>;

export type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>;

/** Deeply omit members of an array of interface or array of type. */
export type DeepOmitArray<T extends any[], K> = {
  [P in keyof T]: DeepOmit<T[P], K>;
};

/** Deeply omit members of an interface or type. */
export type DeepOmit<T, K> = T extends Primitive
  ? T
  : {
      [P in Exclude<RequiredKeys<T>, K>]: T[P] extends infer TP // extra level of indirection needed to trigger homomorhic behavior // distribute over unions
        ? TP extends Primitive
          ? TP // leave primitives and functions alone
          : TP extends any[]
          ? DeepOmitArray<TP, K> // Array special handling
          : DeepOmit<TP, K>
        : never;
    } & {
      [P in Exclude<OptionalKeys<T>, K>]?: T[P] extends infer TP // extra level of indirection needed to trigger homomorhic behavior // distribute over unions
        ? TP extends Primitive
          ? TP // leave primitives and functions alone
          : TP extends any[]
          ? DeepOmitArray<TP, K> // Array special handling
          : DeepOmit<TP, K>
        : never;
    };

/** Deeply omit members of an array of interface or array of type, making all members optional. */
export type PartialDeepOmitArray<T extends any[], K> = Partial<{
  [P in Partial<keyof T>]: Partial<PartialDeepOmit<T[P], K>>;
}>;

/** Deeply omit members of an interface or type, making all members optional. */
export type PartialDeepOmit<T, K> = T extends Primitive
  ? T
  : Partial<{
      [P in Exclude<keyof T, K>]: T[P] extends infer TP // extra level of indirection needed to trigger homomorhic behavior // distribute over unions
        ? TP extends Primitive
          ? TP // leave primitives and functions alone
          : TP extends any[]
          ? PartialDeepOmitArray<TP, K> // Array special handling
          : Partial<PartialDeepOmit<TP, K>>
        : never;
    `}>;`

Maybe there is a better way that I'd be interested to hear but this works for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment