Infer types when using type guard in Typescript

1.1k Views Asked by At
interface A = {
  name: string;
  ...
};

interface B = {
  name: string;
  ...
};

interface C = {
  key: string;
  ...
};

type UnionOfTypes = A | B | C | ...;

function hasName(item: UnionOfTypes) {
  if ("name" in item) {
    item; // typescript knows here that item is either A or B
  }
}

Is there some way that I can infer types in the same way that if("name" in item) does it? I don't have classes only interfaces/types.

So that I don't have to explicitly say

function hasName(item: UnionOfTypes): item is A | B {
  ...
}

The reasoning behind this is that I want to use other type guards deeper down, or is there some other reason why I should avoid narrowing down types this way?

1

There are 1 best solutions below

0
On

There is no support for this by default in typescript. You can create a helper function that creates a type guard. The function passed to this helper function will return the guarded item, or false.

function guard<T extends I, I>(fn: (value: I) => T | false)  {
    return function (value: I) : value is T {
        return fn(value) !== false
    }
}

interface A {
  name: string;
  a: number;
};

interface B {
  name: string;
  b: number;
};

interface C {
  key: string;
};

type UnionOfTypes = A | B | C ;

const hasName = guard(function (item: UnionOfTypes) {
  if ("name" in item) {
    return item;
  }
  return false;
})


// Works for other casses too
let numberOrNull: Array<string | null> = ["", null]
let r = numberOrNull.filter(guard(v => v ?? false));
let r2 = numberOrNull.filter(guard(v => v ?? false));

Playground Link