I am trying to narrow down (w/ inference) the type that I want out of this array filter, but it's giving me an TypeError: 'Item' is missing the following properties
type ItemList = (Item | ItemGroup )[];
type ItemGroup = {
name: string;
items: Item[];
}
type Item = {
key: string;
label: string;
}
const list: ItemList = [
{
key: 'key',
label: 'label'
},
{
name: 'name',
items: [
{
key: 'key1',
label: 'label2'
},
{
key: 'key3',
label: 'label4'
},
]
}
]
const groups: ItemGroup[] = list.filter( l => 'name' in l )
^^^^^^
// Type '(Item | ItemGroup)[]' is not assignable to type 'ItemGroup[]'.
// Type 'Item | ItemGroup' is not assignable to type 'ItemGroup'.
// Type 'Item' is missing the following properties from type 'ItemGroup': name, items ts(2322)
Any ideas?
Unfortunately the compiler isn't smart enough to look at the l => "name" in l
callback and understand that it can be used to narrow an Item | ItemGroup
to just an ItemGroup
. Luckily, you can tell the compiler that this is the intent by annotating it as a user-defined type guard function:
const isItemGroup = (l: Item | ItemGroup): l is ItemGroup => "name" in l;
Now if you call isItemGroup(l)
and the result is true
, the compiler will understand that l
is an ItemGroup
. Additionally, the standard library provides a call signature for Array.prototype.filter()
that accepts a user-defined type guard callback and produces a narrowed array. So by using isItemGroup
as the callback, you get your desired outcome:
const groups: ItemGroup[] = list.filter(isItemGroup); // no error now