TypeScript: remove key from type/subtraction type
Solution 1
While there isn't a built-in subtraction type, you can currently hack it in:
type Sub0<
O extends string,
D extends string,
> = {[K in O]: (Record<D, never> & Record<string, K>)[K]}
type Sub<
O extends string,
D extends string,
// issue 16018
Foo extends Sub0<O, D> = Sub0<O, D>
> = Foo[O]
type Omit<
O,
D extends string,
// issue 16018
Foo extends Sub0<keyof O, D> = Sub0<keyof O, D>
> = Pick<O, Foo[keyof O]>
In the question's case, you would do:
type ExcludeCart<T> = Omit<T, 'cart'>
With TypeScript >= 2.6, you can simplify it to:
/**
* for literal unions
* @example Sub<'Y' | 'X', 'X'> // === 'Y'
*/
export type Sub<
O extends string,
D extends string
> = {[K in O]: (Record<D, never> & Record<string, K>)[K]}[O]
/**
* Remove the keys represented by the string union type D from the object type O.
*
* @example Omit<{a: number, b: string}, 'a'> // === {b: string}
* @example Omit<{a: number, b: string}, keyof {a: number}> // === {b: string}
*/
export type Omit<O, D extends string> = Pick<O, Sub<keyof O, D>>
Solution 2
Update for TypeScript 3.5: The Omit<Type, Keys>
utility type is now available. Please see Mathias' answer for an example usage.
Old Answer: Since TypeScript 2.8 and the introduction of Exclude
, It's now possible to write this as follows:
type Without<T, K> = {
[L in Exclude<keyof T, K>]: T[L]
};
Or alternatively, and more concisely, as:
type Without<T, K> = Pick<T, Exclude<keyof T, K>>;
For your usage, you could now write the following:
type ExcludeCart<T> = Without<T, "cart">;
Solution 3
While this has been correctly answered, I wanted to point out that TypeScript 3.5 did add an Omit<T, E>
type.
type NoCart = Omit<{foo: string, bar: string, cart: number}, "cart">;
This results in the {foo: string, bar: string}
type.
Solution 4
there is another very simple way to have this result
When combining type in typescript, the type "never" have higher priority to everything.
You can simply create a type:
type noCart<T> = T & {cart : never}
Or, without creating type
function removeCart<T>(obj : T) : T & {cart : never} {
if("cart" in obj) {
delete (obj as T & {cart : any}).cart;
}
return <T & {cart : never}> obj;
}
This is less generic than the solution of Adrian, but a bit simpler when we don't need the complexity.
Solution 5
Update: See Adrian's answer above for a solution to this question. I've left my answer here though since it still contains some useful links.
There are various old requests for this feature ("outersection" types, subtraction types), but none have really progressed.
Recently, with the addition of mapped types I asked about this again, and Anders said that while there's no plans to make a general subtraction type operator, a more limited version might be implemented, presumably looking something like this proposal.
I've personally run into quite similar situations to you when working with React, and unfortunately haven't been able to find any good solution. In a simple case, you can get away with something like:
interface BaseProps {
foo: number;
bar: number;
}
interface Inner extends BaseProps {
cart: Cart;
}
interface Wrapper extends BaseProps {
cartClient: Client;
}
but I almost find this to be a semantic abuse of the extends
keyword. And of course, if you don't control the typings of Inner
or BaseProps
, then this won't work out.
Daisy Leigh Brenecki
Updated on July 09, 2022Comments
-
Daisy Leigh Brenecki almost 2 years
I want to define a generic type
ExcludeCart<T>
that is essentiallyT
but with a given key (in my case,cart
) removed. So, for instance,ExcludeCart<{foo: number, bar: string, cart: number}>
would be{foo: number, bar: string}
. Is there a way to do this in TypeScript?Here's why I want to do this, in case I'm barking up the wrong tree: I'm converting an existing JavaScript codebase to TypeScript, which contains a decorator function called
cartify
that takes a React component classInner
and returns another component classWrapper
.Inner
should take acart
prop, and zero or more other props.Wrapper
accepts acartClient
prop (which is used to generate thecart
prop to pass toInner
), and any prop thatInner
accepts, exceptcart
.In other words, once I can figure out how to define
ExcludeCart
, I want to do this with it:function cartify<P extends {cart: any}>(Inner: ComponentClass<P>) : ComponentClass<ExcludeCart<P> & {cartClient: any}>
-
Daisy Leigh Brenecki over 7 yearsThanks for that! Unfortunately I can't really do what you're suggesting, because I want to be able to just type
MyComponent = cartify(MyComponent)
(or@cartify↵class MyComponent…
once that syntax lands) and have TypeScript automatically infer the correct type for the wrapped component. And it's a reusable library, so I definitely can't predictInner
ahead of time either. -
Daisy Leigh Brenecki over 6 yearsThis is great, thanks! All of the previous implementations of
Omit
that I'd tried didn't work on generics, but these ones do. -
JKillian over 6 yearsVery cool! Adrian, did you develop this, or did you find it somewhere else? If it's from somewhere else, can you link to that source? I was trying to look into your answer to see if it had any limitations or any other relevant discussion about it. I've also linked to your answer from mine because it seems to work well
-
Adrian Leonhard over 6 years@JKillian no, I can't remember where I got it from, but various variants are discussed on the corresponding TS issue, for example github.com/Microsoft/TypeScript/issues/…
-
Daisy Leigh Brenecki over 6 yearsI remember going through that exact thread and trying all the variants there around the time I posted this question, and all of the ones I tried didn't work for generics (as in, you could go
Omit<T, 'foo'>
whereT
was some concrete type, but something likefunction myfunc<T extends {foo: any}>(in: T) : Omit<T, 'foo'>
would break with rather confusing errors). So I'm not sure if it's a different way of definingOmit
that popped up later or an improvement/bugfix in TS, but I think your answer was correct at the time it was posted, @JKillian :) -
Henrik R almost 6 yearsThis works pretty good. One thing I noticed was that if
T
has types that are optional, after usingWithout
they are all required. Do you know why that is? -
Daisy Leigh Brenecki almost 6 yearsThe docs also mention that
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
will also work, in the 2.8 release notes (down the bottom of the linked section). This version ofOmit
seems to work as expected with optional members too. -
James McMahon over 4 yearsThis is a good answer, I couldn't figure out how to scale it to multiple keys. I took an attempt at a more direct use of exclude in another answer. That approach scales rather easily.
-
Mathias over 4 yearsI believe simply
Exclude<{ foo: number, bar: string, cart: number }, "cart">
will do the trick. -
Mathias over 4 yearsYou're right! I was thinking
Omit<{foo: number, bar: string, card: number}, "cart">
which achieves the correct result. Again. thanks for double checking! Should I delete the comment above? -
James McMahon over 4 yearsOh neat. I will have to try that out. Up to you on the comment.
-
A Jar of Clay about 2 yearsTo omit multiple, do
type NoCart = Omit<{foo: string, bar: string, cart: number}, "cart" | "bar">;