Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript type narrowing not working when looping

Tags:

typescript

I'm probably missing something obvious here, but I've been stuck on this one for a while.

Given the following predicate (I know it's not technically a predicate):

const hasProp = <K extends PropertyKey, T extends {}> (obj: T, prop: K): obj is T & Record<K, unknown> => {
  return Object.prototype.hasOwnProperty.call(obj, prop);
}

I want to do something like this:

const coreProps = ['id', 'module', 'name', 'auth'] as const;
for (const prop of coreProps) {
  if (!hasProp(rawObject, prop)) {
    throw new Error(`Missing property '${prop}'`);
  }
}

However, this doesn't let me use e.g. rawObject.id afterwards. If I manually unroll the loop, it works as expected.

Am I doing something wrong, or is this a limitation of the TypeScript compiler?


I did come up with this alternative:

const hasProps = <Ks extends PropertyKey[], T extends {}> (obj: T, ...props: Ks): obj is T & { [K in Ks[number]]: unknown } => {
  return props.every(prop => hasProp(obj, prop));
}

But I don't like this, because it doesn't return any information about which property is missing (if any).

like image 625
MTCoster Avatar asked Apr 02 '26 21:04

MTCoster


1 Answers

I believe you should use assert function for this purpose:

const hasProp = <K extends PropertyKey, T>(obj: T, prop: K): obj is T & Record<K, unknown> => {
  return Object.prototype.hasOwnProperty.call(obj, prop);
}

const rawObject: any = {}

const coreProps = ['id', 'module', 'name', 'auth'] as const;

type Props = typeof coreProps;


function assert(value: unknown): asserts value is Record<Props[number], unknown> {
  for (const prop of coreProps) {
    if (!hasProp(rawObject, prop)) {
      throw new Error(`Missing property '${prop}'`);
    }
  }
}


assert(arg);// <---- assert function
arg.id // ok

Playground

Assertion function examples

TS official docs

Hence, in order to make it work, you should wrap your loop into assert function. In this way, TS is able to infer the type of your object, otherwise it wont be able to do it because of mutable nature of objects.

like image 79
captain-yossarian Avatar answered May 04 '26 14:05

captain-yossarian