Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript error handling an unknown variable: Property does not exist on type 'object'

Tags:

typescript

I'm trying to understand how to properly approach handling an unknown variable, which may be an object with certain properties I can use. This derives from a variety of try/catch scenarios when working with a third party library which is still in development, so "e" may or may not be an object with certain properties.

Below is a simplified version of my question, but there are other manifestations of the issue. Therefore, I wish to avoid type assertions or disable type checking. What I'm looking for is a proper way to check an "unknown" value to see if it is an object with certain properties.

Why is the following if condition insufficient? TypeScript just views it as an unknown object and isn't use the info I've passed to ensure (1) it is an object, (2) it is not null, and (3) it has the key "errNum".

What other possible condition could cause "console.log(e.errNum)" to throw an error at runtime after all of these checks? This fails both in the VSCode GUI and when building with webpack 5 (using eslint after recent migration from tslint). I can't think of any other edge cases that would cause problems at runtime.

try {
    const err = { errNum: 12 };
    throw err;
} catch (e) {
    // e is inferred as unknown, as it should be
    if (e && typeof e === 'object' && 'errNum' in e) console.log(e.errNum); // TS error: Property 'errNum' does not exist on type 'object'.
}
like image 328
Robert T Avatar asked Jul 28 '26 13:07

Robert T


2 Answers

You will not be able to take advantage of the type checker's inference capabilities because the catch clause variable is unknown. In this case the easier things might be to use the condition to determine that errNum is property in a type guard function.

catch (e)
{
  if (isErrorNumObj(e)) console.log(e.errNum);
}

function isErrorNumObj<T>(obj: T): obj is T & { errNum: unknown } {
  return obj && 'errNum' in obj;
}

You can even make a more generic version that can accept any number of props.

try {
  const err = { errNum: 12, stackTrace: 'abc' };
  throw err;
} catch (e: unknown) {

  if (hasProps(e, 'errNum', 'stackTrace')) {
    console.log(e.errNum, e.stackTrace);
  }
}

function hasProps<T, U extends string | number | symbol>(obj: T, ...propName: U[]): obj is T & { [P in U]: unknown } {
  return !!obj // ensure not nullish.
    && (typeof obj == "object" || typeof obj == "function") // ensure not primitive.
    && propName.every(x => x in obj); // ensure every prop is in obj.
}
like image 177
Daniel Gimenez Avatar answered Jul 31 '26 16:07

Daniel Gimenez


I'd create a user defined type predicate.

function isErrNum(x: any): x is { errNum: number } {
    return !!x && 'errNum' in x;
}

try {
    const err = { errNum: 12 };
    throw err;
} catch (e: unknown) {
    // e is inferred as unknown, as it should be
    if (isErrNum(e)) {
        console.log(e.errNum);
    }
}

We extract our "test" code into a function that returns "variable is type". And a "true" return then informs the compiler that in the context of that being true, the object is that declared type and is not that type in an else.

TypeScript Playground

like image 36
crashmstr Avatar answered Jul 31 '26 17:07

crashmstr