Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is a version of DeepOmit that recursively removes keys possible? #104

Open
breathe opened this issue Nov 29, 2019 · 9 comments
Open

Is a version of DeepOmit that recursively removes keys possible? #104

breathe opened this issue Nov 29, 2019 · 9 comments
Labels
enhancement New feature or request v10.1

Comments

@breathe
Copy link

breathe commented Nov 29, 2019

Is it possible to write a DeepOmit that recursively applies the filter ...?

type Input = {
  __typename: string;
  a: string;
  nested: {
    __typename: string;
    b: string;
  };
  nestedArray: Array<{
    __typename: string;
    b: string;
  }>;
  nestedTuple: [
    {
      __typename: string;
      b: string;
    }
  ];
};

type InputWithoutKey = DeepOmit<Input, { __typename: never }>;

const iWishThisWouldTypeCheck: InputWithoutKey = {
  a: "",
  nested: {
    b: ""
  },
  nestedArray: [{ b: "" }],
  nestedTuple: [{ b: "" }]
};

I get type errors for iWishThisWouldTypeCheck because of missing __typename fields ...

In my case -- a version that simply took string arguments would work fine if that is easier to accomplish somehow ... -- eg DeepOmit<Input, "__typename">

@quezak
Copy link
Collaborator

quezak commented Nov 29, 2019

You'd need a separate type for that. If we decide there are enough usecases for it, we could write one, it would even be simpler than the specific DeepOmit.

The question here: what should be the type constraint for the filter here? Omit requires that the key in argument is a key of the passed type, and DeepOmit does so recursively. But here, you apply the same filter to potentially different types, does that mean any key names should be allowed?

@Beraliv Beraliv added the enhancement New feature or request label Aug 15, 2021
@simon-abbott
Copy link

For anyone else looking for this I have a solution that works in TS 4.9. It was adapted from https://gist.github.com/ahuggins-nhs/826906a58e4c1e59306bc0792e7826d1 (which itself was adapted from https://stackoverflow.com/a/55539616/3438793). The key(s) to exclude has no relation to the type, just like the built-in Omit type. I don't know how to make a Strict variant of this (or even if such a type is possible), but this solves my use case perfectly.

/** 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 DeepOmitKey<T, K extends keyof any> = T extends Primitive
  ? T
  : T extends any[]
  ? { [P in keyof T]: DeepOmitKey<T[P], K> }
  : {
      [P in Exclude<keyof T, K>]: T[P] extends infer TP // Distribute over unions
        ? DeepOmitKey<TP, K>
        : never;
    };

@Beraliv
Copy link
Collaborator

Beraliv commented Mar 11, 2023

@simon-abbott thank you for the suggestion! Would you like to see it like that in this library? The use case is clear for me based on StackOverflow example, I'm happy to add it, test cases and examples if anyone or you are interested

@simon-abbott
Copy link

It would be useful, yes! There aren't many use cases for it, but where it's needed it's really useful. I'm using the above code in our codebase, so being able to import it from ts-essentials instead would be great. In fact, the reason I found this issue was because I was hoping ts-essentials already had this built in, and when I was trying to find it I came across this issue asking for it.

@Beraliv
Copy link
Collaborator

Beraliv commented Mar 15, 2023

It would be useful, yes! There aren't many use cases for it, but where it's needed it's really useful. I'm using the above code in our codebase, so being able to import it from ts-essentials instead would be great. In fact, the reason I found this issue was because I was hoping ts-essentials already had this built in, and when I was trying to find it I came across this issue asking for it.

Great! I will start working on it when finish other PRs

Let me know if you have other good examples that I can add to the documentation. If not, I will leave __typename as the only one

@simon-abbott
Copy link

I could make up some synthetic examples, but the only realistic use-case I can currently think of is __typename (and its siblings like type and kind) .

@simon-abbott
Copy link

After trying this out on more code, I realized that my first draft discards optionality, which isn't ideal. Here is a second version that preserves optionality:

type Primitive =
  | string
  | Function
  | number
  | boolean
  | symbol
  | undefined
  | null;

type OptionalKeys<T> = {
  [K in keyof T]-?: T extends { [P in K]: any } ? never : K;
}[keyof T];

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

// This is technically optional, but without it the resulting type is full of
// intersections, which is hard to read. For example, if this is left out, the
// type `DeepOmitKey<{a?: string; b: number; c: boolean}, 'b'>` gets resolved as
// `{a?: string} & {c: boolean}` instead of `{a?: string; c: boolean}`.
type ResolveIntersection<T> = T extends infer O
  ? { [K in keyof O]: O[K] }
  : never;

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

@Beraliv
Copy link
Collaborator

Beraliv commented Jun 15, 2023

@simon-abbott LGTM! I will need to run some checks before adding this enhancement. Let me know if you'd like to contribute yourself, I will be able to help with it. Otherwise I will add it to my list and will come back to you once the PR is ready

@simon-abbott
Copy link

Go for it! I'm in no rush for this to be added (we just have that definition in our own codebase for now), I just want to save the 5 others who will find this useful the trouble of having to invent it themselves. 😄

@Beraliv Beraliv added v10.1 and removed help wanted Extra attention is needed labels May 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request v10.1
Projects
None yet
Development

No branches or pull requests

4 participants