Home > database >  Typescript: Extract from discriminated union with multiple identical types
Typescript: Extract from discriminated union with multiple identical types

Time:11-29

I have a type like below:

type Entity =
  | {
      type: 'user' | 'person';
      id: string;
    }
  | {
      type: 'animal';
      id: number;
    };

Now I want to extract the types from it:

type Animal = Extract<Entity, {type: 'animal'}> - works perfectly (returns { type: 'animal'; id: number; });

type User = Extract<Entity, {type: 'user'}> - returns never.

How can I make it return { type: 'user'; id: string; } ?

CodePudding user response:

The provided documenation for Extract<T, U> says:

Extract from T those types that are assignable to U

And { type: 'user' | 'person', id: string } is not assignable to { type: 'user' }

declare const entity: Entity
const user: { type: 'user' } = entity
/*
Type 'Entity' is not assignable to type '{ type: "user"; }'.
  Type '{ type: "user" | "person"; id: string; }' is not assignable to type '{ type: "user"; }'.
    Types of property 'type' are incompatible.
      Type '"user" | "person"' is not assignable to type '"user"'.
        Type '"person"' is not assignable to type '"user"'.(2322)
*/

Which means you cannot use Extract to do this.


But what you can do is use an intersection (&), which will trim away any union members that cannot be matched by that discriminant.

type User = Entity & { type: 'user' }

const user: User = { type: 'user', id: 'abc' }
user.id // type: string

See Playground

CodePudding user response:

Extracting the first element of the union using Extract does not work as 'user' | 'person' is not assignable to 'user'. We need to write our own CustomExtract which matches for Partial<T>.

type CustomExtract<T, U> = 
  T extends T 
    ? U extends Partial<T> 
      ? T 
      : never
    : never

type Animal = CustomExtract<Entity, { type: 'animal' }>
// type Animal = {
//     type: 'animal';
//     id: number;
// }

type User = CustomExtract<Entity, { type: 'user' }>
// type User = {
//     type: 'user' | 'person';
//     id: string;
// }

Playground

  • Related