Now we have Mapped Types and keyof, we could add a subtraction operator that only works on literal types:
type Omit<T, K extends keyof T> = {
[P in keyof T - K]: T[P];
};
type Overwrite<T, K> = K & {
[P in keyof T - keyof K]: T[P];
};
type Item1 = { a: string, b: number, c: boolean };
type Item2 = { a: number };
type T1 = Omit<Item1, "a"> // { b: number, c: boolean };
type T2 = Overwrite<Item1, Item2> // { a: number, b: number, c: boolean };
Duplicate of #4183?
I'm going to relabel this a suggestion as #4183 doesn't actually have a proposal.
@ahejlsberg checkout my proposal at #4183
Hope to see this soon, will be a huge improvement too!
Note that keyof T
may be string
(depending on available indexers), and therefore the behavior of string - 'foo'
, etc. will need to be defined.
Edit: corrected that keyof T
is always string, however the point of string - 'foo'
remains.
Note that keyof T may be string or number or string | number (depending on available indexers), and therefore the behavior of string - 'foo', and number - '1', etc. will need to be defined.
this is not accurate. keyof
is always a string
.
If there are any questions about use cases, this would be incredibly helpful for typing Redux, Recompose, and other higher order component libraries for React. For instance, wrapping an uncontrolled dropdown in a withState HOC removes the need for isOpen or toggle props, without the need to manually specify a type.
Redux's connect() similarly wraps and supplies some/all props to a component, leaving a subset of the original interface.
Poor man's Omit
:
type Omit<A, B extends keyof A> = A & {
[P in keyof A & B]: void
}
The "omitted" key is still there, however mostly unusable, since it's type is T & void
, and nothing sane can satisfy this constraint. It won't prevent you from accessing T & void
, but it will prevent you from assigning it or using it as a parameter in a function.
Once https://github.com/Microsoft/TypeScript/pull/13470 lands, we can do even better.
@niieani That solution won't work for react HoC that inject props.
If you use this pattern to "remove" props, the typechecker will still complain if you omit them when using a component:
type Omit<A, B extends keyof A> = A & {
[P in keyof A & B]: void
}
class Foo extends React.Component<{a:string, b:string}, void> {
}
type InjectedPops = {a: any}
function injectA<Props extends InjectedPops>(x:React.ComponentClass<Props>):React.ComponentClass<Omit<Props, 'a'>>{
return x as any
}
const Bar = injectA(Foo)
var x = <Bar b=''/>
// Property 'a' is missing in type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<Omit<{ a: string; b: string; }, "a">, Co...'.
I think this one would be easier to realise in the following form:
[P in keyof A - B]
should work only if A extends B, and return all keys that are in A and are not in B.
type Omit<A extends B, B> = {
[P in keyof A - B]: P[A]
}
type Impossible<A, B> = {
[P in keyof A - B]: string
} /* ERROR: A doesn't extend B */
interface IFoo {
foo: string
}
interface IFooBar extends IFoo {
bar: string
}
type IBar = Omit<IFooBar, IFoo>; // { bar: string }
Since we cannot change types of fields when extending, all inconsistencies (a.k.a) string - 'world' would go away.
Overwrite
seems covered by #10727. This proposal seems an alternate syntax for #13470?
Overwrite
using today's syntax:
type Item1 = { a: string, b: number, c: boolean };
type Item2 = { a: number };
type ObjHas<Obj extends {}, K extends string> = ({[K in keyof Obj]: '1' } & { [k: string]: '0' })[K];
type Overwrite<K, T> = {[P in keyof T | keyof K]: { 1: T[P], 0: K[P] }[ObjHas<T, P>]};
type T2 = Overwrite<Item1, Item2> // { a: number, b: number, c: boolean };
Edit: fixed an indexing issue.
I also tried my hand at Omit
, if with mixed success:
// helpers...
type Obj<T> = { [k: string]: T };
type SafeObj<O extends { [k: string]: any }, Name extends string, Param extends string> = O & Obj<{[K in Name]: Param }>;
type SwitchObj<Param extends string, Name extends string, O extends Obj<any>> = SafeObj<O, Name, Param>[Param];
type Not<T extends string> = SwitchObj<T, 'InvalidNotParam', {
'1': '0';
'0': '1';
}>;
type UnionHas<Union extends string, K extends string> = ({[S in Union]: '1' } & { [k: string]: '0' })[K];
type Obj2Keys<T> = {[K in keyof T]: K } & { [k: string]: never };
// data...
type Item1 = { a: string, b: number, c: boolean };
// okay, Omit, let's go.
type Omit_<T extends { [s: string]: any }, K extends keyof T> =
{[P2 in keyof T]: { 1: Obj2Keys<T>[P2], 0: never }[Not<UnionHas<K, P2>>]}
type T1 = Omit_<Item1, "a">;
// intermediary result: { a: never; b: "b"; c: "c"; }
type T2 = {[P1 in T1[keyof Item1] ]: Item1[P1]}; // { b: number, c: boolean };
// ^ great, the result we want!
Wonderful, yet another problem solved!
...
// now let's combine the steps?!
type Omit<T extends { [s: string]: any }, K extends keyof T> =
{[P1 in {[P2 in keyof T]: { 1: Obj2Keys<T>[P2], 0: never }[Not<UnionHas<K, P2>>]}[keyof T] ]: T[P1]};
type T3 = Omit<Item1, "a">;
// ^ yields { a: string, b: number, c: boolean }, not { b: number, c: boolean }
// uh, could we instead do the next step in a separate wrapper type?:
type Omit2<T extends { [s: string]: any }, K extends keyof T> = Omit_<T, K>[keyof T];
// ^ not complete yet, but this is the minimum repro of the part that's going wrong
type T4 = Omit2<Item1, "a">;
// ^ nope, foiled again! 'a'|'b'|'c' instead of 'b'|'c'... wth?
// fails unless this access step is done after the result is calculated, dunno why
Note that my attempt to calculate union differences in #13470 suffered from the same problem... any TypeScript experts in here? 馃槃
@tycho01 what you're doing here is amazing. I've played around with it a bit and the 1-step behavior does seem like a bug in TypeScript. I think if you file a separate bug report about it, we could get it solved and have a wonderful one-step Omit
and Overwrite
! :)
@niieani: yeah, issue filed at #16244 now.
I'd scribbled a bit on current my understanding/progress on TS type operations; just put a pic here. Code here.
It's like, we can do boolean-like operations with strings, and for unions/object operations the current frontier is overcoming this difference glitch.
Operations on actual primitives currently seem off the table though, while for tuple types things are still looking tough as well -- we can get union/object representations using keyof
/ Partial
, and hopefully difference will work that way too. I say 'hopefully' because some operations like keyof
seem not to like numerical indices...
Things like converting those back to tuple types, or just straight type-level ...
destructuring, aren't possible yet though.
When we do, we may get to type-level iteration for reduce-like operations, where things get a lot more interesting imo.
Edit: strictNullChecks
issue solved.
@tycho01 ,
Amazing thing indeed!!
I played with it a bit, what do you think about this solution?
type Obj<T> = { [k: string]: T };
type SafeObj<O extends { [k: string]: any }, Name extends string, Param extends string> = O & Obj<{[K in Name]: Param }>;
type SwitchObj<Param extends string, Name extends string, O extends Obj<any>> = SafeObj<O, Name, Param>[Param];
type Not<T extends string> = SwitchObj<T, 'InvalidNotParam', {
'1': '0';
'0': '1';
}>;
type UnionHas<Union extends string, K extends string> = ({[S in Union]: '1' } & { [k: string]: '0' })[K];
type Obj2Keys<T> = {[K in keyof T]: K } & { [k: string]: never };
type Omit_<T extends { [s: string]: any }, K extends keyof T> =
{[P2 in keyof T]: { 1: Obj2Keys<T>[P2], 0: never }[Not<UnionHas<K, P2>>]}
export type Omit<T extends { [s: string]: any }
, K extends keyof T
, T1 extends Omit_<T, K>= Omit_<T, K>
, T1K extends keyof Pick<T1, keyof T>=keyof Pick<T1, keyof T>> =
{[P1 in T1[T1K]]: T[P1]}
;
type Item1 = { a: string, b: number, c: boolean };
const ommited: Omit<Item1, 'a'> = {
b: 6,
c: true,
}
@nirendy seems to be working for me! 馃憤
@tycho01 great job on solving the strictNullChecks
issue.
@niieani: you can thank @jaen for that, see #13470. 馃槂
@nirendy: whoa! I hadn't realized we can use this defaults syntax like that. the repetition feels a bit awkward, but being able to save/reuse results and check them for debugging purposes is pretty awesome!
That said... separating the steps here was one thing, but also swapping out the [keyof T]
for that keyof Pick<>
? How did you ever figure any of this out?! 馃槅
that's amaze balls guys, how did you do it?
@tycho01
I feel like there is no way to avoid that repetition there (while using the current version tools) since it plays a different role each time (once it's there to make sure the generic actually extends the type and the other one is there in order to omit the need of providing all the generics params).
Any way, I think we managed to achieve an amazing thing!
It doesn't really matter to me that the code came out pretty awkward, I'll just place it in a declaration file and use it in a similar way of how I use the Pick, Partial, etc... types.
Amazing!
@nirendy: yeah, no complaints here! 馃槂
edit: @aleksey-bykov: as for my parts at least, step by step, from the ground up (see #16392).
Some fun stuff here. I just merged #16446 which allows an even simpler solution:
type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] };
type Overwrite<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] } & U;
type T1 = Diff<"a" | "b" | "c", "c" | "d">; // "a" | "b"
type Item1 = { a: string, b: number, c: boolean };
type Item2 = { a: number };
type T2 = Omit<Item1, "a"> // { b: number, c: boolean };
type T3 = Overwrite<Item1, Item2> // { a: number, b: number, c: boolean };
The Diff
type is effectively a subtraction operator for string literal types. It relies on the following type equivalences:
T | never
= T
T & never
= never
(which #16446 provides)Furthermore, it relies on the fact that an object with a string index signature can be indexed with any string.
A native literal subtraction operator should include certain higher order type relationships. For example, given type parameters T
and U
, T - U
should be considered a subtype of T
, and { [P in keyof T - U]: T[P] }
should be considered a supertype of T
. The Diff
type doesn't have those, so type assertions may in some cases be necessary.
I noticed a small technical difference in that the cleaner Overwrite
definition yields an intersection type:
type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Overwrite<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] } & U;
type Item1 = { a: string, b: number, c: boolean };
type Item2 = { a: number };
type T3 = Overwrite<Item1, Item2> // { b: number, c: boolean } & { a: number }
The following alternative, adding an extra Pick
step, appears to 'fix' that, for whatever it's worth.
type Overwrite<T, U, Int = { [P in Diff<keyof T, keyof U>]: T[P] } & U> = Pick<Int, keyof Int>;
... as far as I know that seems little more than an aesthetic difference. oh well!
Edit: I take that back; I no longer recommend using this Pick
-based version. It actually appears to subtly break a few things like my attempts at re-typing Ramda's fromPairs
/ zipObject
here.
I tried using this (the one from @tycho01 and/or @nirendy, not the new one from @ahejlsberg yet) to omit a property from a class that overrides toString()
, and it didn't work. Here's a minimal example:
type Item1 = { a: string, b: number, c: boolean, toString(): string };
type OmittedItem1 = Omit<Item1, "a">
// OmittedItem1 should be { b: number, c: boolean, toString(): string }, but is {}!
I can kind of imagine why overriding toString()
and company (uh... valueOf()
? maybe all other prototype methods?) would wreak havoc on anything that enumerates keys, but it seems unfortunate.
Anyone want to speculate whether this is intended behavior of Omit
, a bug in Omit
, or a bug in Typescript that breaks Omit
?
@jcalz:
Yeah, I've had considerable issues with toString
(and other Object.prototype
method toLocaleString
).
I'm not getting {}
though -- which TS version? Playground and newer seem better.
Edit: the problem appear to lie in Diff
: it works by doing & { [P in U]: never } & { [x: string]: never }
. In the event of toString
though, these two object types are expected to have a toString
method, which messes them up. I'm gonna omit toLocaleString
for brevity.
For the latter, it seems possible to do say { toString: "toString"; [k: string]: never }
to overwrite it. The tough thing is like, I don't think you can make it never
here cuz that'd get overwritten by the prototype method...
For the former though, a syntax like { toString: "something"; [P in U]: never }
appears not to work for me, yielding A computed property name must be of type 'string', 'number', 'symbol', or 'any'
, or having the latter half seemingly getting ignored.
A syntax { [P in keyof U]: never; [K in keyof PrototypeMethods]: "something"; }
would be even more desirable here but again no dice. Then again, the question is still what we'd overwrite it with rather than "something"
, since you'd just wanted whatever was otherwise specified, but either way even when you wanna overwrite toString
as never
, you can't. Essentially just about all operations currently get screwed up with toString
.
I suppose combining these object syntaxes has been not exactly common, and this case might not actually be supported.
... Any comment on that, @ahejlsberg?
Still kinda thinking about alternatives... it's definitely a tough one though.
Edit 2: one issue here is ambiguity in { [k: string]: 123 }['toString']
-- should toString
be expected to be among k
, resolving to 123
, or should it just resolve to the prototype method?
Currently TS presumes the latter, which makes it hard to serve use-cases where you intend the former.
Essentially I'd wanna try to construct an alternative way to access object properties that is immune to this, instead checking only own keys rather than prototype methods as well.
I've made a bit of progress, but it's hard to make anything without using the original property access operator, so kinda need to go over things to prevent buggy behavior from sneaking in everywhere.
I'm not getting
{}
though -- which TS version? Playground and newer seem better.
This is amazing! It appears that @nirendy's version doesn't work with optionals:
type Item1 = { a: string, b?: number, c: boolean }
const omitted: Omit<Item1, 'a'> = { c: true }
// Property 'b' is missing in type '{ c: true; }'.
@jcalz: late response but thanks for the repro. seems resolving it may not be trivial though since property access and most operations depending on it are affected; will try further at one point.
@dleavitt:
Thanks for the bug report. I tried a bit; looks like, as you noted, Omit
(either version) doesn't currently reproduce the ?
. With strictNullChecks
it instead appends a | undefined
to the value, which seems not good enough yet to make this type-check. I'm not yet sure of a way to address this that wouldn't require type-level type checks (potentially solved by e.g. #6606) to check whether undefined
would satisfy the object properties...
@tycho01 could we solve the ?
issue by typing it multi-pass?
i.e.:
Omit
on the non-optional type| undefined
to the existent keys of the omitted type@niieani:
- re-add the | undefined to the existent keys of the omitted type
This doesn't actually cut it; with strictNullChecks
we already get back { b: number | undefined; c: boolean; }
, so the ?
appears to matter.
I'll need to admit I don't fully understand what's going on yet. If you use { [P in keyof Item1]: P }
the ?
appears preserved just fine, meaning the info appears available in keyof Item1
, though if you check that itself you don't get to see any additional info. Looks like a magical invisible property, not having checked compiler internals.
2: stash the list of optional properties only, in a generic, aside
Checking whether a property is optional is currently a challenge. Doing a keyof
seems not to give this info (in a visible way); number | undefined
is clear to us, but the amount of type-level operators is severely limited -- if we know undefined
satisfies that type we're good, but on the type level, as it stands, we don't have a way to do that.
// insert `Omit` typing, one or the other depending on whether you use Playground or 2.5
type Item1 = { a: string, b?: number, c: boolean }
type noA = Omit<Item1, 'a'>
// { b: number | undefined; c: boolean; }
// ^ `Omit` degenerates `?` to ` | undefined`
type keys = keyof Item1
// "a" | "b" | "c"
type almost1 = { [P in keyof Item1]: If<ObjectHasKey<noA, P>, (noA & Obj<never>)[P], never> }
// { a: never; b?: number | undefined; c: boolean; }
type almost2 = { [P in keyof Item1]: If<ObjectHasKey<noA, P>, P, never> }
// { a: never; b?: "b"; c: "c"; }
The thing that gets us 'stuck' at that point is that the only way to ditch a: never
prop is by plugging almost2
's keys into itself, though that would simultaneously make us lose out on b
's ?
status. In almost1
we're closer to where we wanna go, but no closer to ditching a
in a way that'd leave b
's ?
intact.
While we cannot directly detect or operate on ?
itself, operating on it by proxy through checks for | undefined
seems a fair enough solution. But yeah, no type-level type checks. Short of upvoting that feature request (or alternatives, I recently saw a comment requesting type-level instanceof
+ boolean-based type-level conditionals), I dunno.
@niieani: alternatively, say | undefined
should be considered equivalent to ?
and file as bug. That could be controversial (I'm not sure if I'd consider it more elegant myself), but it'd be less involved for the TS side so for your purposes potentially better chances of a fix.
@tycho01 yeah, I see it. I think the TypeScript team mentioned somewhere that | undefined
should be considered equivalent to ?
-- i.e. it aligns with their goals. For most intents and purposes this is how TS currently behaves, although I see what you mean by compiler differentiating somehow.
For example Flow treats these (?
and | void
) as different things, i.e. you could technically have a property and expect its type to be undefined
. However for (all?) practical reasons they are pretty much the same.
@niieani: Yeah. Seems appropriate to file a bug then.
excuse me for being off topic
@jcalz practically speaking toString
can be erased from the declaration files, this way it will only appear on the classes/objects that explicitly implement it (provided the default implementation is useless anyway)
this is how it can be done:
noLib: true
in tsconfig.json
https://github.com/Microsoft/TypeScript/tree/master/src/lib
and include them to your projecttoString
of the Object
and comment it outinterface Object {
/** The initial value of Object.prototype.constructor is the standard built-in Object constructor. */
constructor: Function;
/** Returns a string representation of an object. */
// toString(): string; <--- HERE
@dleavitt @niieani filed it at #16762.
@tycho01 @dleavitt using Pick
preserves optionals in both versions of Omit
. I'm not sure if this breaks anything else though.
Here's a full example with the concise 2.4 definition.
type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;
interface A {
a: string;
b?: string;
c: number;
}
type B = Omit<A, 'c'>;
const a: A = {
a: '5',
c: 5,
};
const b: B = {
a: '5',
};
// Note: typeof B is (correctly) Pick<A, 'a' | 'b'>
Similarly, you can redefine Omit
in the legacy version (rest of the definition is _omitted_).
type Omit<T extends { [s: string]: any }
, K extends keyof T
, T1 extends Omit_<T, K>= Omit_<T, K>
, T1K extends keyof Pick<T1, keyof T> = keyof Pick<T1, keyof T>> = Pick<T, T1[T1K]>;
@Pinpickle: I tried for a bit with the other tests I had. I only see your Omit
definition fixing issues, no cons found! 馃槂
@Pinpickle this is also working "well enough" with the toString()
override, or at least workaroundably so. ("Workaroundably" is a word, right?)
type Item1 = { a: string, b: number, c: boolean, toString(): string };
type OmittedItem1 = Omit<Item1, "a">; // { b: number, c: boolean } almost?
type OmittedItem1Fixed = OmittedItem1 & { toString(): string }; // good enough
I was reminded by a comment in #12424, current Omit
and Overwrite
implementations still fail recreate any string index the source object types might have held. Checking for index presence could be done given #6606.
@ahejlsberg @Pinpickle and others - thanks with the help on this! Using the above technique, we were able to replicate flow's $Diff type. This allowed us to correctly handle defaultProps
in React:
/**
* From https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458
* The Diff type is a subtraction operator for string literal types. It relies on:
* - T | never = T
* - T & never = never
* - An object with a string index signature can be indexed with any string.
*/
export type StringDiff<T extends string, U extends string> = ({[K in T]: K} &
{[K in U]: never} & {[K: string]: never})[T];
/**
* From https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766
* Omits keys in K from object type T
*/
export type ObjectOmit<T extends object, K extends keyof T> = Pick<T, StringDiff<keyof T, K>>;
/**
* Returns a version of type T where all properties which are also in U are optionalized.
* Useful for makding props with defaults optional in React components.
* Compare to flow's $Diff<> type: https://flow.org/en/docs/types/utilities/#toc-diff
*/
export type ObjectDiff<T extends object, U extends object> = ObjectOmit<T, keyof U & keyof T> &
{[K in (keyof U & keyof T)]?: T[K]};
deleted question made before switching brain to javascript mode :-) (sorry for the spam)
@rob3c:
type OmitFromBoth<T, K extends keyof T> = Omit<T & typeof T, K | 'prototype'>;
type WorksForBoth = OmitFromBoth<Hmm, 'I1' | 'S1'>;
// "I1" | "S2"
Like that? Untested, but you just wanted your last example shortened right?
@tycho01 I tried that already as well as other variations, but none worked. There doesn't seem to be a way to reference T's prototype within the generic definition. That's probably due to javascript not allowing access to 'static' prototype properties from an instance (unlike, say, Java). That's why I deleted the question, but you answered too fast :-)
@rob3c: So no typeof
on T
huh? I see what you're getting at then; I hadn't really tried things like that so far. I'll admit I'm not aware of workarounds in that case. I'm slowly discovering debugging with the compiler is a bit less bad than I thought, but still not too great.
@robc there actually are ways to access prototype
in a generic context (I'm now curious about what the question was 馃榿).
EDIT typo fixed
@aluanhaddad The question was essentially about writing a version of Omit
above that returns filtered keyof
properties for both instance and static members of a type while only needing to write Omit<MyClass, ...>
and not Omit<MyClass & typeof MyClass, ...>
. I was saying prototype
above, but it should have been constructor
, since that's where the 'static' members are stuck by typescript (I think I got distracted by trying to filter out the 'prototype' property in my code and the example). And for various reasons documented elsewhere, the constructor
property is only typed as Function
, so all variations I tried that pulled keys from the 'constructor' member only resulted in those on Function
without the static props declared in the class.
Anyway, although interesting in terms of generic type wrangling, it's irrelevant in my particular case, since you can't directly access static members from an instance anyway in javascript lol (I blame it on momentary language blindness due to insufficient coffee.)
The original code was something like this:
type Diff<T extends string, U extends string> = (
& { [P in T]: P }
& { [P in U]: never }
& { [x: string]: never }
)[T];
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;
class Hmm {
readonly I1 = 'I1';
readonly I2 = 'I2';
static readonly S1 = 'S1';
static readonly S2 = 'S2';
};
// "I2"
type WorksForInstancePropsOnly = base.Omit<Hmm, 'I1'>;
/// "prototype" | "S1" | "S2"
type HasAnnoyingPrototypeKey = keyof typeof Hmm;
// "S2"
type WorksForStaticPropsOnly = base.Omit<typeof Hmm, 'S1' | 'prototype'>;
// "I1" | "S2"
type WorksForBoth = base.Omit<Hmm & typeof Hmm, 'I1' | 'S1' | 'prototype'>;
type OmitForBoth<T, ...> = ???
@rob3c
Anyway, although interesting in terms of generic type wrangling, it's irrelevant in my particular case, since you can't directly access static members from an instance anyway in javascript lol (I blame it on momentary language blindness due to insufficient coffee.)
that statement is inaccurate.
it is possible ofc
class Hmm {
static s1 = 'static one'
static s2 = 'static two'
i1 = 'instance one'
i2 = 'instance two'
}
const foo = new Hmm()
console.log(foo)
const {s1, s2} = foo.constructor
console.log({s1, s2})
@Hotell
that statement is inaccurate.
Actually, my statement IS accurate, but you may have misunderstood my terminology :-)
Unlike Java, for example, you can't directly access static members on an instance (e.g. myInstance.myStaticProp
). Your example shows an attempt at indirect access via the instance's constructor
property - which typescript will complain about btw with corresponding red squigglies in vscode, because that's only available at runtime. That's why I lamented constructor
being only typed as Function
above, instead of something more strongly-typed (see https://github.com/Microsoft/TypeScript/issues/3841 and related).
Anyway, the question pertains to generic type definitions using keyof
and such for the purposes of strongly-typed intellisense and overall type-checking while writing code, not what's really available at runtime. Since the intention behind my use of keyof
was for strongly-typing the available declared directly-accessible properties, their availability on constructor
at runtime is irrelevant (as stated above).
Even with respect to just the theoretical question on whether it's possible to combine both in a generic type definition using only T
, the answer is no without explicitly typing constructor
on the classes of interest, since it's only typed Function
in general.
I'm trying to use Omit type as suggested by @aldendaniels in this comment:
export type RegionsTableProps =
ObjectOmit<TableProps, 'headerHeight' | 'rowHeight' | 'rowCount'> &
{
regions: Region[];
onRegionClick: (id: string) => void;
};
class RegionsTable extends React.PureComponent<RegionsTableProps> {}
where TableProps
is from @types/react-virtualized (width?: number;
)
However, when I try to use RegionsTable like this:
table = ({ width, height }: Dimensions) => (
<RegionsTable
regions={this.props.regions.list}
onRegionClick={this.onRegionClick}
width={width}
height={height}
/>
);
I get error:
TS2339: Property 'width' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<RegionsTable> & Readonly<{ children?: ReactNode; }...'.
If I replace ObjectOmit<TableProps, 'headerHeight' | 'rowHeight' | 'rowCount'>
with Partial<TableProps>
everything compiles.
I don't quite understand what is going on and how to fix this. Anyone got any ideas?
@doomsower it's probably due to [key: string]: any
in GridCoreProps
// A = never
export type A = StringDiff<keyof TableProps, 'headerHeight' | 'rowHeight' | 'rowCount'>
(ts 2.4.2)
@ahejlsberg Diff
, Omit
and Overwrite
are awesome, can we expect them to make it into the standard library at some point alongside Record
, Partial
, Readonly
, Pick
, etc.?
for Overwrite
, which version would be considered preferable?
For those interested, I've added Diff
, Omit
and Overwrite
to Type Zoo.
@pelotom there's already https://github.com/gcanti/typelevel-ts and https://github.com/tycho01/typical
@goodmind the more the merrier
Agreed, while we've yet to even settle on all the right best practices we definitely need more competition. Type libs still face significant issues in testing with e.g. non-terminating types (usually bugged recursive types) -- rather than having a single unit test fail your whole compilation just fails to terminate. I'd love to see more people try their own take on things on whatever level.
I've noticed an issue with Omit
and generic functions, or at least with one of the implementations. Not quite sure how to describe it, so I'll stick with a code sample:
type Diff<T extends string, U extends string> = ({ [P in T]: P } &
{ [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] };
interface BaseType {
x: string;
}
const fnWithDefault = <T extends BaseType>(
input: Omit<T, keyof BaseType>
): T => {
// Error: Spread types may only be created from object types
const attempt1 = { ...input, x: 'foo' };
// Error: Type 'Omit<T, "x"> & { x: string; }' is not assignable to type 'T'.
return Object.assign({}, input, { x: 'foo' });
};
fnWithDefault<{ x: string; y: string }>({ y: 'bar' });
fnWithDefault
is a function that adds x
to the result, returning T
, and thus the input type should be T
without x
. TypeScript, however, doesn't seem to allow this with either spread or Object.assign
.
Basically, I would expect Omit<T, 'x'> & { x: string }
to be assignable to T
. (assuming that T.x
is a string). Not sure if this is a problem in TypeScript, or with the Omit
implementation, or is even by design.
Getting back for a while to the Omit
implementations. I've created a minimal example with various implementations of Omit
. Then I test them on interface with optional and readonly fields. One of them, as someone mentioned earlier, doesn't respect optional fields. So, I tested it again, substituting parts of Omit
with underlaying implementations. Look at the code below:
// built-in Pick definition for reference
// type Pick<T, K extends keyof T> = {[P in K]: T[P]; };
// well-known Diff implementation
type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];
// our example interface with some optional fields
interface Example {
a?: number;
readonly b: boolean;
c?: string;
readonly d: any;
}
// Omit implementation which doesn't respect optional fields and readonly
type Omit<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
type ExampleSubsetA = Omit<Example, 'a' | 'b'>;
// ExampleSubsetA = { c: string; d: any; }
// Omit implementation using Pick, which works good with optional fields and readonly
type OmitUsingPick<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;
type ExampleSubsetB = OmitUsingPick<Example, 'a' | 'b'>;
// ExampleSubsetB = { c?: string; readonly d: any; }
// ok, so we now substitute Diff with its body, resulting in
// Omit implementation with Pick but without Diff, that still works good with optional fields and readonly
type OmitUsingPickWithoutDiff<T, K extends keyof T> = Pick<T, ({[P in keyof T]: P } & {[P in K]: never } & { [x: string]: never })[keyof T]>;
type ExampleSubsetC = OmitUsingPickWithoutDiff<Example, 'a' | 'b'>;
// ExampleSubsetC = { c?: string; readonly d: any; }
// well, so now we substitute Pick with its body resulting in
// Omit implementation without any other interfaces, which suddenly stopped respecting optional fields and readonly
type OmitSelfsufficient<T, K extends keyof T> = {[P in ({[P in keyof T]: P } & {[P in K]: never } & { [x: string]: never })[keyof T]]: T[P]};
type ExampleSubsetD = OmitSelfsufficient<Example, 'a' | 'b'>;
// ExampleSubsetD = { c: string; d: any; }
(Code on TypeScript Playground)
My questions is, why getting rid of Pick results of optional and readonly modifiers being lost in process? For me it seems like some bug in TypeScript itself.
It looks like there was a recent change in TypeScript@next that breaks a popular implementation of Diff
and Omit
:
type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T]
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>
It has been working for a while, but now errors (using dtslint which is pulling latest typescript@next
ERROR: 2:45 expect TypeScript@next compile error:
Type '({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof T]' does not satisfy the constraint 'keyof T'.
Type '({ [P in T]: P; } & { [P in U]: never; })[keyof T]' is not assignable to type 'keyof T'.
I tried various Omit
implementations listed by @aczekajski and they get various errors:
type Omit<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
ERROR: 1:44 expect TypeScript@next compile error:
Type '({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof T]' is not assignable to type 'string'.
Type '({ [P in T]: P; } & { [P in U]: never; })[keyof T]' is not assignable to type 'string'.
ERROR: 1:63 expect TypeScript@next compile error:
Type 'P' cannot be used to index type 'T'.
md5-b82dbd8360a59e843eda22c0d24ea876
ERROR: 1:54 expect TypeScript@next compile error:
Type '({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof T]' does not satisfy the constraint 'keyof T'.
Type '({ [P in T]: P; } & { [P in U]: never; })[keyof T]' is not assignable to type 'keyof T'.
```ts
type OmitUsingPickWithoutDiff<T, K extends keyof T> = Pick<T, ({[P in keyof T]: P } & {[P in K]: never } & { [x: string]: never })[keyof T]>
ERROR: 1:65 expect TypeScript@next compile error:
Type '({ [P in keyof T]: P; } & { [P in K]: never; } & { [x: string]: never; })[keyof T]' does not satisfy the constraint 'keyof T'.
Type '({ [P in keyof T]: P; } & { [P in K]: never; })[keyof T]' is not assignable to type 'keyof T'.
md5-220859cd669f13e25d02f34222d801f3
ERROR: 1:58 expect TypeScript@next compile error:
Type '({ [P in keyof T]: P; } & { [P in K]: never; } & { [x: string]: never; })[keyof T]' is not assignable to type 'string'.
Type '({ [P in keyof T]: P; } & { [P in K]: never; })[keyof T]' is not assignable to type 'string'.
ERROR: 1:138 expect TypeScript@next compile error:
Type 'P' cannot be used to index type 'T'.
```
this pr might have broken it: https://github.com/Microsoft/TypeScript/pull/17912
While the expected behavior of Omit
is clearly broken in current @next, someone willing to fix it must be aware that as I've shown in my previous comment, it also have some weird inconsistent behavior in 2.6.2.
@aleksey-bykov Fix is up at #21156.
@aczekajski We are aware of the weird behaviour, and it results from a few different not-quite-sound systems interacting. I consider these types "use at your own risk".
I think the Spread
type in #21316 is essentially a superior version of Overwrite
here.
Commenting here for folks who got to this issue page from the net after the release of 2.8.
With TS 2.8 the more convenient way of implementing Omit
now the below, taking advantage of conditional types and new built-in type Exclude
:
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
Along those lines, here's an implementation of Overwrite
:
type Overwrite<T1, T2> = {
[P in Exclude<keyof T1, keyof T2>]: T1[P]
} & T2;
Or, once you have Omit
,
type Overwrite<T, U> = Omit<T, Extract<keyof T, keyof U>> & U;
Since there is a new, better Omit
, I hoped to do something that seemed impossible with previous TS versions. Let's say I have interfaces like that:
interface P1 {
type: 1,
a: string;
}
interface P2 {
type: 2,
b: string;
}
type P = { id: number } & (P1 | P2);
What I want to achieve is to get rid of id
field and still have a discriminated union. However, when I do Omit<P, 'id'>
what I get is no longer a discriminated union, fields a
and b
disappear during the Exclude
phase.
What is interesting, simply rewriting a type P
with something like that:
type Rewrite<T> { [K in keyof T]: T[K] };
won't break the union. So I thought about using conditional types to omit the "Exclude" part. So I've written this:
type OmitFromUnion<T, X extends keyof T> = { [K in keyof T]: (K extends X ? never : T[K]) };
Logic in my mind behind this: if rewriting can maintain discriminated union than let's do this but if this particular K
is what we want to omit, make it never
(instead of excluding this key in the mapping part). This failed miserably, doing effectively nothing (producing discriminated union but still with this id
fieldn in it).
Might it be still impossible until there is a method to get all possible keys of discriminated union (for all cases)?
@aczekajski If you want Omit
to distribute over unions, you can define it like this:
type Omit<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never
which uses the fact that conditional types automatically distribute the checked type over a union if the checked type is a bare type parameter (T
in the above case).
You can verify that the above Omit<P, 'id'>
behaves like P1 | P2
.
@jcalz Ok, when reading the docs I totally missed the fact that it automatically distributes over a unions. At first glympse, this T extends any
seems like it is always true and as such does nothing, so I don't really like the "hackery feeling" about this solution, but old Diff
was even more of a hackery.
Summarizing, isn't the omit stated by above comment the better one than the previous solution which brakes unions?
By the way, it's now possible to get all keys for all discriminated union cases than:
type AllPossibleKeys<T> = T extends any ? keyof T : never;
Is there a way with 2.8 Exclude
etc to prevent optional properties being spat out as type | undefined
here?
type Overwrite<T1, T2> = { [P in Exclude<keyof T1, keyof T2>]: T1[P] } & T2;
type A = Overwrite<{ myOptional?: string, myRequired: number }, { myRequired: string }>;
// compiler complains Property 'myOptional' is missing in type '{ myRequired: string; }'
let x: A = {
myRequired: 'hello'
};
@drew-r Can't you just use NonNullable
@drew-r try this solution: https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-378367303
@drew-r Use this for Overwrite
:
type Overwrite<T1, T2> = Pick<T1, Exclude<keyof T1, keyof T2>> & T2
Pick
is specially treated by the compiler and will preserve modifiers in the original type, instead of transforming them, like homomorphic mapped types.
@ferdaber, thanks for that interesting bit o' trivia
Pick is specially treated by the compiler and will preserve modifiers in the original type, instead of transforming them, like homomorphic mapped types.
More information can be found in the docs:
Readonly, Partial and Pick are homomorphic whereas Record is not. One clue that Record is not homomorphic is that it doesn鈥檛 take an input type to copy properties from:
type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>
Non-homomorphic types are essentially creating new properties, so they can鈥檛 copy property modifiers from anywhere.
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
I know this was dropped from the TypeScript codebase, but I see myself and colleagues need this nearly weekly and in every new project. We just use it _so much_ and because it is like the "opposite" of Pick
I just feel more and more that this should ship with TypeScript by default. It is especially hard for new TS developers who see Pick
and look for Omit
and don't now about this GitHub thread.
Is there a way to remove a wide type from a union type without also removing its subtypes?
export interface JSONSchema4 {
id?: string
$ref?: string
// to allow third party extensions
[k: string]: any
}
type KnownProperties = Exclude<keyof JSONSchema4, string | number>
// I want to end up with
type KnownProperties = 'id' | 'ref'
// But, somewhat understandably, get this
type KnownProperties = never
// yet it seem so very within reach
type Keys = keyof JSONSchema4 // string | number | 'id' | 'ref'
Try this:
type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K
} extends {[_ in keyof T]: infer U} ? U : never;
I'm on it, captain!
@ferdaber, it absolutely worked, you are a genius!
Is that somewhat based on the original Diff
hack?
I didn't even think to try conditional types here.
I see it's based on the fact that string
extends string
(just as 'a'
extends string
) but string
doesn't extend 'a'
, and similarly for numbers.
First it creates a mapped type, where for every key of T, the value is:
Then, it does essentially valueof
to get a union of all the values:
type ValuesOf<T> = T extends { [_ in keyof T]: infer U } ? U : never
```ts
interface test {
req: string
opt: string
}
type FirstHalf
}
type ValuesOf
// or equivalently, since T here, and T in FirstHalf have the same keys,
// we can use T from FirstHalf instead:
type SecondHalf
type a = FirstHalf
//Output:
type a = {
[x: string]: never;
req: "req";
opt?: "opt" | undefined;
}
type a2 = ValuesOf // "req" | "opt" // Success!
type a2b = SecondHalf // "req" | "opt" // Success!
// Substituting, to create a single type definition, we get @ferdaber's solution:
type KnownKeys
} extends { [_ in keyof T]: infer U } ? U : never;
// type b = KnownKeys
@ferdaber That is amazing. The trick is in how infer
works... it apparently iterates through all the keys, both "known" (I'd call that "literal") keys and index keys, and then gives the union of the results. That differs from doing T[keyof T]
which only ends up extracting the index signature. Very good work.
@qm3ster. you can indeed distinguish optional keys from required keys whose values may be undefined
:
type RequiredKnownKeys<T> = {
[K in keyof T]: {} extends Pick<T, K> ? never : K
} extends { [_ in keyof T]: infer U } ? ({} extends U ? never : U) : never
type OptionalKnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : {} extends Pick<T, K> ? K : never
} extends { [_ in keyof T]: infer U } ? ({} extends U ? never : U) : never
which produces
type c = RequiredKnownKeys<test> // 'reqButUndefined' | 'req'
type d = OptionalKnownKeys<test> // 'opt'
All credit goes to @ajafff! https://github.com/Microsoft/TypeScript/issues/25987#issuecomment-408339599
@jcalz
{} extends Pick<T, K>
Why, I'd never!
Y'all a bunch of hWizards in here or something?
@ferdaber too late, I credited you on StackOverflow and now they'll come get you.
No good deed goes unpunished.
Like @donaldpipowitch indicates above, can we please have Omit
in TypeScript as well. The current helper types like Exclude
and Pick
are super useful. I think Omit
is also something that comes in very handy. We can always create it ourselves with
type Omit
= Pick >
But having it built-in instead of always having to lookup this type would be super nice! I can always open a new issue to discuss this further.
The Omit
helper type has official support as of TypeScript 3.5
https://devblogs.microsoft.com/typescript/announcing-typescript-3-5/#the-omit-helper-type
Most helpful comment
Some fun stuff here. I just merged #16446 which allows an even simpler solution:
The
Diff
type is effectively a subtraction operator for string literal types. It relies on the following type equivalences:T | never
=T
T & never
=never
(which #16446 provides)Furthermore, it relies on the fact that an object with a string index signature can be indexed with any string.
A native literal subtraction operator should include certain higher order type relationships. For example, given type parameters
T
andU
,T - U
should be considered a subtype ofT
, and{ [P in keyof T - U]: T[P] }
should be considered a supertype ofT
. TheDiff
type doesn't have those, so type assertions may in some cases be necessary.