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
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;
// }