1

Let's say I have a Vehicle union type that looks like that:

interface Car {
  color: string;
  doors: number;
  wheels: number;
}

interface Plane {
  color: string;
  wings: number;
}

type Vehicle = Car | Plane;

I want (1) to extract from this union the types that extend a given type argument (this can easily be achieved using the Extract utility type) and (2) to omit from the extracted type(s) the keys of that same type argument. Here's the desired output:

type Result1 = ExtractOmit<{ wheels: number; }>;
// This should give me the result of Omit<Car, "wheels">:
// { color: string; doors: number; }

type Result2 = ExtractOmit<{ wings: number; }>;
// This should give me the result of Omit<Plane, "wings">:
// { color: string; }

type Result3 = ExtractOmit<{ foo: number; }>;
// This should produce an error since foo is neither in Car nor Plane

Here's an unsuccessful attempt:

type ExtractOmit<T> = Omit<Extract<Vehicle, T>, keyof T>;
// T extends what?

The right part seems right to me but I can't figure out how to put up the left one.

Any suggestion?

TypeScript playground

tavoyne
  • 5,591
  • 2
  • 14
  • 24

1 Answers1

2
interface Car {
    color: string;
    doors: number;
    wheels: number;
}

interface Plane {
    color: string;
    wings: number;
}

type Vehicle = Car | Plane;

type OmitExtract<T extends Partial<Vehicle>> = Omit<Extract<Vehicle, T>, keyof T>;

type Result = OmitExtract<{ wheels: number; }>;

type Result1 = OmitExtract<{ wheels: number; }>;
// This should give me the result of Omit<Car, "wheels">:
// { color: string; doors: number; }

type Result2 = OmitExtract<{ wings: number; }>;
// This should give me the result of Omit<Plane, "wings">:
// { color: string; }

The above seems to work as expected.

The key pieces here being using "Vehicle" (I noticed you used Element earlier) and having T extend the Partial on the left to give it the supertype necessary for Extract to know what to extract from correctly.

Edit: playground link

Azarro
  • 1,776
  • 1
  • 4
  • 11
  • 1
    You actually don't even need the "T extends Partial" unless you just really want to restrict the typing of T's supertype. Having Vehicle on the right hand side is what's most important to setup the Extract utility type. – Azarro May 07 '22 at 09:13
  • 2
    You need to use `type Vehicle = Car | Plane;` as per OP's post. OP's original playground had the wrong code with `type Vehicle = Car & Plane` which will give the wrong result as in your playground. Union is what's needed on Vehicle to setup Vehicle appropriately. – Azarro May 07 '22 at 09:14
  • Thanks! Yes, sorry for the confusion with `Element`, I meant `Vehicle` as you guessed. – tavoyne May 07 '22 at 09:14
  • I noticed there's one edge case that's not covered by this. It's when `{ color: string; }` is passed as an argument. Since both `Car` and `Plane` extend `{ color: string; }`, I'd expect the result to be a union of those (from which `color` would be omitted): `{ doors: number; wheels: number; } | { wings: number; }`. But all I get is `{}`. Any idea how we might take this case into account? – tavoyne May 07 '22 at 09:41
  • 1
    Ok all we need is a distributive Omit (see https://stackoverflow.com/questions/57103834/typescript-omit-a-property-from-all-interfaces-in-a-union-but-keep-the-union-s). – tavoyne May 07 '22 at 09:47
  • https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgMJysg3gKGchAewBtCoAuZAZzClAHMBuPZAE0LKspAFcBbAEbRm+AO4ALCBGJdkvQcJwBfHDlCRYiFAAVicEClz4ipCtVoMRyUQ1nyhUZipxgAngAcUANQjjgCYhQAXjQMZAAfZF19CGYXDxQAEWAaOgEeMGAANwgAeT5gMAAeABUAGmQAaWQIAA9IEFYqZABrCFdCGGR9VwA+ZBCSmvqIRuaelgB+ZHzC0orK3pZuCBzHVTdPZABReqhEMFniobqGpqiMTLhiIp8-AIhe-pDk1OB0zJyjot3aA9vfP5AhUSr0Km0Ol1QXFNigAEoQKg8YhgACMAx2ewO3yw1kk0js-AcjGQSl6zAA9BTkCU-M0UsNPAhIKwNglkAikSiAEwY377Zk46y2bhE4Sk8k4Kk0unIBl1JkstlbTnIsAAZj5WMFBWKuJgHFFCkcEsp1NpDPltUVEFZ8RViLVABYtX8dXNcSYyJQ3iB6KapebZVabaygA – tavoyne May 07 '22 at 09:49
  • 1
    Was just about to post the same after coming across the stackoverflow thread! Hope that worked. Learning a lot more about ts' utility types today haha – Azarro May 07 '22 at 09:52
  • In general, I've found that there are a lot of places in which typescript doesn't allow good Union splitting (i.e. dealing with an explicit "A | B" as a type when it comes to checking equality/assignments). – Azarro May 07 '22 at 09:53