Reputation: 373
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?
Upvotes: 3
Views: 7261
Reputation: 42170
You have a array which contains both Item
and ItemGroup
elements. You want to filter this array to just the elements which are ItemGroup
, and you want typescript to understand that you have filtered the list and know that the returned type is ItemGroup[]
.
How you can achieve this is buy turning the filter l => 'name' in l
into its own type guard function. The return type value is ItemGroup
tells typescript "if and only if this is true, value is of type ItemGroup".
const isItemGroup = (value: Item | ItemGroup): value is ItemGroup => 'name' in value;
const groups: ItemGroup[] = list.filter( isItemGroup );
By using a type guard, typescript can understand the meaning of list.filter
and your error goes away.
Upvotes: 5
Reputation: 327774
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
Upvotes: 3
Reputation: 7428
You can assert that your filtered resulting array is of type ItemGroup[]
using type assertion:
const groups: ItemGroup[] = list.filter( l => 'name' in l ) as ItemGroup[]
Upvotes: 0