typescripttype-conversionunion-typestypeguards

How to properly find array element and cast when array has union types and generics?


I have the following type declarations:

types.ts:


type ItemKind = 'A' | 'B';

type ValidItem<TItemKind extends ItemKind = ItemKind> = {
    readonly type: TItemKind;
    readonly id: number;
};

type EmptyItem<TItemKind extends ItemKind = ItemKind> = {
   readonly type: `empty-${TItemKind}`;
};

export type Item = ValidItem | EmptyItem;

Now, given an array of Items I want to find an item element by their id property and access the value.

I'm able to provide a predicate function that finds the element but I haven't been able to figure out how to use and cast the object once the match happens.

index.ts:

import { type Item } from './types';

const items: Items[] = [
    {type: 'A', id: 42},
    {type: 'empty-A'},
    {type: 'empty-B'}
];

console.debug('Items', items);

const targetId = 42;
const item = items.find((item) =>
  item.type === 'A' && item.id === targetId // This works
);

if (item) {
   console.debug('Item', item);

   // typecast and access id property??
   // i.e. 
   // console.debug('Id', item.id); // this gives me error
}

You can run it like this:

$ npx ts-node index.ts

Items [ { type: 'A', id: 42 }, { type: 'empty-A' }, { type: 'empty-B' } ]
Item { type: 'A', id: 42 }

Any help is greatly appreciated. I'm unable to change the type structure or declarations.


Solution

  • You can refine the item type by adding a type assertion to the callback:

    const item = items.find((item): item is Item & {type: 'A'} =>
      item.type === 'A' && item.id === targetId
    );