Let's assume I want to declare a list of healthy foods in TypeScript:
type Fruit = {
name: string;
color: string;
};
type Vegetable = {
name: string;
taste: string;
};
type HealthyStuff = Array<Fruit | Vegetable>;
const healthyStuff: HealthyStuff = [
{ name: "apple", color: "red" },
{ name: "banana", color: "yellow" },
{ name: "turnip", taste: "boring" },
];
And now I want to filter my list of healthyStuff
using a Array.filter()
to remove everything that smells like a Vegetable
. I want to print the color of the remaining items.
I'm using a type predicate to do the narrowing:
function isVegetable(item: Fruit | Vegetable): item is Vegetable {
return "taste" in item;
}
const tastyStuff = healthyStuff
.filter((item) => !isVegetable(item))
.map((item) => item.color); // error
When I try the above, TypeScript is giving me an error in my .map()
invocation: Property 'color' does not exist on type 'Fruit | Vegetable'.
If I replace the filter()
call with a classic for
loop everything works as expected:
const tastyStuff = [];
for (const item of healthyStuff) {
if (!isVegetable(item)) {
tastyStuff.push(item);
}
}
tastyStuff.map((item) => item.color); // this works
Is there a way I can narrow my union type in an Array.filter()
call?
For some reason, the functionality for inferring type predicates from function bodies doesn't currently work with negating known type predicates. There is a feature request to support that at microsoft/TypeScript#58996, and that issue is listed as "Help Wanted" so they will entertain pull requests from the community. There's even a candidate PR at microsoft/TypeScript#59155 which aims to implement it. If that gets merged, then your code will work as-is.
Until and unless that happens you'll have to work around it: the most expedient workaround is to annotate the return type of your arrow function as the desired type predicate:
const tastyStuff = healthyStuff
.filter((item): item is Fruit => !isVegetable(item))
.map((item) => item.color);