Home > Software design >  Property 'type' does not exist on type 'object', even after `"type" in
Property 'type' does not exist on type 'object', even after `"type" in

Time:08-15

This code fails to compile:

const nodeIsUseless = (node: unknown) =>
  node !== null &&
  typeof node === "object" &&
  "type" in node &&
  typeof node.type === "string" &&
  node.type === "JSXText";

because on the last 2 lines:

Property 'type' does not exist on type 'object'.(2339)

...which I can understand by itself, but I do not understand why after "type" in node check, TS infers node to still be of type object instead of type { type: unknown; [key: string]: unknown }, which would not trigger error.

CodePudding user response:

You should do this:

const nodeIsUseless = (node: unknown) =>
  node !== null &&
  node !== undefined &&
  node instanceof Object &&
  !Array.isArray(node) &&
  "type" in node &&
  typeof node["type"] === "string" &&
  node["type"] === "JSXText";

Just need to check insonceof instead of typeof and use object["key"] method to access values instead of .key. Also, it's good practice to ensure that the item isn't an Array since instonceof [] === 'object' too

CodePudding user response:

Unfortunately, the TypeScript built-in in operator type guard is not as powerful as you expect it to be.

From a bare object, it will not infer that the tested property is available. It can infer that it is indeed there, only if it is already potentially available, e.g. in one of a union-ed types. I.e. the control flow will not make the tested property "appear", but only try to differentiate between union-ed types.

declare const o: object;
if ("type" in o) {
  o.type // Error: Property 'type' does not exist on type 'object'.
//^? object
}

declare const u: Number | String; // Note: using class only for the sake of the demo
if ("toFixed" in u) {
  u.toFixed(); // Okay
//^? Number
}

Playground Link


In your case, you could therefore specify a union for the node argument, with a possible type of e.g. { type: unknown }.

However, the unknown top type absorbs all other types in a union, so it would have to be replaced by everything else, e.g. using the special type {} to represent common types:

const nodeIsUseless = (node: undefined | null | {} | { type: unknown }) =>
  node !== null &&
  typeof node === "object" &&
  //     ^? {} | { type: unknown } | undefined
  "type" in node &&
  //        ^? {} | { type: unknown }
  typeof node.type === "string" && // Okay
  //     ^? { type: unknown }
  node.type === "JSXText"; // Okay

Playground Link

  • Related