6

Supposed I have two interface, X and Y, that both share a few fields, but have independent fields as well:

interface X {
  abc: number;
  foo: number;
  bar: number;
}

interface Y {
  abc: number;
  foo: number;
  baz: number;
}

Now I create a union of those types:

type Z = X | Y;

The resulting type is either X or Y, which is fine. Now I remove one of the common fields using Omit:

type limitedZ = Omit<Z, 'foo'>;

What I'd expect is that limitedZ has the following form:

{ abc: number, bar: number } | { abc: number, baz: number }

Instead, the independent fields are gone and all that is left is the abc field which is shared by both. Why is that?

here is a demo link

Rachid O
  • 13,013
  • 15
  • 66
  • 92
Golo Roden
  • 140,679
  • 96
  • 298
  • 425
  • Weird. If I do `const x: limitedZ = {abc: 1, bar: 1, baz: 1};` the error message is: "*Type '{ abc: number; bar: number; baz: number; }' is not assignable to type 'Pick*". This suggests to me that `Omit` is defined in terms of `Pick` which in turn causes a problem with union types as you cannot claim that `limitedZ` *has* a `bar` or a `baz` property. Probably because it works with the combined result which is `{abc: number; foo: number;}` which somehow has at least one of `bar` or `baz` but are also optional. – VLAZ Jan 21 '21 at 09:32
  • 1
    You probably can find some answers here: https://stackoverflow.com/questions/57103834/typescript-omit-a-property-from-all-interfaces-in-a-union-but-keep-the-union-s – gbalduzzi Jan 21 '21 at 10:29
  • 1
    "The resulting type is either X or Y" - this is not quite true. It is rather "X or Y or both" (inclusive OR), whereas XOR would be a discriminated union type. As for the why part: `keyof`, which is part of `Omit`, only [returns all *common* properties](https://stackoverflow.com/questions/49401866/all-possible-keys-of-an-union-type). – ford04 Jan 21 '21 at 12:07

1 Answers1

3

In order to accomplish what you want using unions you can do this - but its not doing what you expect

type limitedZ = Omit<X, 'foo'> | Omit<Y, 'foo'>

A union denoted by a pipe is almost an XOR so the type that results must be one or the other, so X or Y, or the keys common to both types (so not true xor). This is why you get the unexpected output. If you want to simply remove foo from both types this will work, but its not creating a type where the keys of X and Y are merged (as it may seem). This will explain this part better than me.

I assume when you call Omit<Z, 'foo'> typescript creates a type formed from the common keys in both types, so abc and foo, and then you remove foo so only abc is left.

If you want to create a type that has the keys of x, and the keys of y - you would need to do something like this which creates an intersection type.

type ThirdType = X & Y

This type now has all the keys of X, and all the keys of Y. Then you could constrict this to remove foo - which is I think what you want to do.

type FourthType = Omit<ThirdType, 'foo'>

Or do it all in one line

type limitedZ = Omit<X & Y, 'foo'>

Does this help explain whats going on a bit?

omeanwell
  • 1,847
  • 1
  • 10
  • 16