typescripttype-conversiondiscriminated-union

Narrow return type of find from discriminated union array


I often use code like in example below and was wondering if there is some smart way to type the find results without having to do explicit type assertion.

type Foo = { type: "Foo" };
type Goo = { type: "Goo" };
type Union = Foo | Goo;

const arr: Union[] = [];
const foo = arr.find(a => a.type === "Foo") as Foo;

If the as Foo type assertion is left out, the result is of type Union even though it can only return type Foo.

What's the cleanest way of fixing the type of find to return the narrowed type in examples like these?

Edit: This problem could be also applicable to filter and other similar methods.

Edit2: Suggested similar question's accepted answer (Way to tell TypeScript compiler Array.prototype.filter removes certain types from an array?) shows that by using type guard in the predicate for find/filter the return value can be narrowed down.

How should this type guard function look to narrow down any discriminated union if e.g. the distinguishing string literal is always under type key?


Solution

  • By now we can have it as a reasonably generic util:

    export function isOfType<
      GenericType extends string,
      Union extends { type: GenericType },
      SpecificType extends GenericType,
    >(val: SpecificType) {
      return (obj: Union): obj is Extract<Union, { type: SpecificType }> =>
        obj.type === val;
    }
    
    const found = arr.find(isOfType('Foo'));
    

    Inspired by Trevor Dixon via https://stackoverflow.com/a/61102770