Property 'type' does not exist on type 'object', even after `"type" in` check
Asked Answered
T

3

7

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.

Tautomer answered 14/8, 2022 at 21:54 Comment(2)
I can't think of any way to satisfy the compiler here without a cast and a runtime check inside a user-defined type guard, hopefully someone cleverer than I sees this. I also realize that isn't really the crux of the question which is about inference.Gentry
Uh oh, looks like even Matt Pocock is struggling with this :/Tautomer
T
6

While the code is correct, TypeScript doesn't have the capability to infer { type: unknown } from "type" in node but this feature is currently in development


2022-09-21 update: with the merge of the aforementionned PR, this can now be done in typescript@next and should be available in typescript@latest soon.

See also the discussion on meta about this answer.

Tautomer answered 8/9, 2022 at 10:59 Comment(2)
this feature has been implemented. With the merge of "Improve checking of in operator #50666", TypeScript does have that capability.Melanymelaphyre
Had to switch my typescript config from 4.8.3 to 4.9.5.Barfield
O
4

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

Octodecillion answered 14/8, 2022 at 22:11 Comment(1)
What version of typescript did you do this with? Doesn't seem to be working for meHalimeda
S
4

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

Sawyor answered 15/8, 2022 at 3:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.