Search code examples
javascripttypescripttypeguardsnarrowingtype-narrowing

Narrowing down to common JS methods and properties in TypeScript


Is there a way to make this TypeScript narrowing less ugly (or more elegant ;-) ❓

With IsSomething type guard, I want to narrow down access to common methods and properties of any JavaScript variable which is not null and not undefined (things like .toString, .valueOf, .hasOwnProperty, .constructor etc.)

Playground link

export type Something = 
  number | string | boolean | symbol | object | bigint;

export function IsSomething(value: unknown) : value is Something {
  return value !== undefined && value !== null;
}

let a = [1, "2", 3];
Narrowing(a);
NotNarrowing(a);

function Narrowing(o: unknown) { // OK
    if (IsSomething(o)) {
      console.log(o.toString(), o.constructor);
    }
}

function NotNarrowing(o: unknown) { // NOT OK
    if (o !== null && o !== undefined) {
      console.log(o.toString(), o.constructor);
    }
}

Solution

  • In TypeScript, every value except for null and undefined is assignable to the so-called empty object type {}. So you can simplify your Something to just {} and your user-defined type guard function will work the same as before:

    export type Something = {};
    function isSomething(value: unknown): value is Something {
      return value !== undefined && value !== null;
    }
    function narrowing(o: unknown) { // OK
      if (isSomething(o)) {
        console.log(o.toString(), o.constructor);
      }
    }
    

    Furthermore, TypeScript 4.8 will introduce improved support for narrowing unknown, after which your "NotNarrowing" function will start working, because checking (o !== null && o !== undefined) will narrow o from unknown to {} automatically:

    // TS4.8+ 
    function nowNarrowing(o: unknown) { // okay
      if (o !== null && o !== undefined) {
        console.log(o.toString(), o.constructor);
      }
    }
    

    Playground link to code