typescripttypestype-narrowing

Typescript narrowing of keys in objects when passed to function


Why does TypeScript not apply type narrowing on members of objects to the object type itself, such that it can be passed to another function that expects a narrowed type? How can this be fixed/circumvented without loosing typesafety?

Minimal example:

type A = { key: string | null};
type B = {key: string};

function func(a: B) {};

const a: A = {key:'abcd'};
if(typeof(a.key)==='string') {
  a.key // has (narrowed) type 'string'
  func(a); // still does not work
}

The error message is: Types of property 'key' are incompatible. Type 'string | null' is not assignable to type 'string'.

Playground-Link


Solution

  • This is ultimately a design limitation of TypeScript. It does not use type guards on an object's properties to narrow the object itself, except in the particular case where the object is a discriminated union type... and in your case, A isn't a union at all, let alone a discriminated one.

    The way I'd do this for your case is to introduce a user-defined type guard function which explicitly performs the narrowing you expect: you pass in an object with a key property, and return true or false depending on whether or not this object is a valid B:

    const isB = (x: { key: any }): x is B => typeof x.key === "string";
    

    Then you use it:

    if (isB(a)) {
      func(a); // okay
    }
    

    This is (give or take a function call) essentially the same logic as your code, but the compiler recognizes your intent now. Okay, hope that helps; good luck!

    Link to code