Mike de Klerk
Mike de Klerk

Reputation: 12328

Why does TypeScript's Omit not enforce the value of the omitted properties?

Background: (can be skipped)

I was fiddling around to make some update(person: Person) statement more usable by allowing a subset of properties it should update. I was thinking there are 2 options:

So Omit seems like a good candidate to me, but the keys to omit can be of any string value.

Question:

Why does TypeScript not enforce the value of the keys provided to Omit ? Is there any good use not to which I am overseeing?

See the following code

export interface Person {
  id: string;
  age: number;
  name: string;
}

type UpdatePerson = Omit<Person, 'whatEver'>; // Why does 'whatEver' not cause compile issues?
type UpdatePerson2 = Pick<Person, 'id'> & Partial<Person>; // Great, a valid key is enforced here
type UpdatePerson3 = Pick<Person, 'thisDoesNotCompile'> & Partial<Person>; // This line fails compilation as that property isn't part of the interface

For now I go with update(person: UpdatePerson) and type UpdatePerson = Pick<Person, 'id'> & Partial<Person>;. This makes all properties optional, and the id mandatory.

Upvotes: 3

Views: 1951

Answers (2)

Dan Philip Bejoy
Dan Philip Bejoy

Reputation: 4381

The built-in Omit utility in TypeScript is implemented like this:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Notice that K is constrained by keyof any, which allows any string-based keys, even those not present on T. This design allows for flexibility but sacrifices strictness regarding the keys in K.

To make a safer version of Omit that ensures only keys present on T can be omitted, you can redefine it with a stricter constraint on K:

type StrictOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

In this custom StrictOmit, K is constrained specifically to keyof T, meaning only properties actually existing on T can be specified for omission. If you try to omit a key that doesn’t exist on T, TypeScript will raise an error.

To wrap things up just place the custom type in the global.d.ts and youre good to go.

Upvotes: 1

Jeff Bowman
Jeff Bowman

Reputation: 95614

In short, this behavior was selected so the built-in Omit could be most compatible with existing DefinitelyTyped libraries. Those libraries were already split on whether Omit should be strict, and picking a looser Omit prevented those existing libraries from breaking at the expense of slightly less compiler safety and autocomplete for code yet to be written.


For background: this was requested in microsoft/TypeScript#30825, with this response from DanielRosenwasser:

It seems like the constrained Omit type is going to make at least half of users unhappy based on declarations within DefinitelyTyped. We've decided to go with the more permissive built-in which your own constraints can build on.

And these from language co-author Ryan Cavanaugh:

I have to clear up this misconception. There were 12 different definitions of Omit on DT and the two most popular definitions differed on whether or not to constraint [sic] the key: [snip]

You can pick at the numbers and try to declare a democratic majority or something, but the reality is that only one definition doesn't break a substantial portion of people.

It is a popular request with duplicates here on SO and on GitHub, and corresponding concise strict equivalents are easy to write/derive and also available in other libraries like type-zoo.

Upvotes: 4

Related Questions