luismartinezs
luismartinezs

Reputation: 199

Why "key in myObject" does not narrow the type if key is of type string?

I don't understand this:

const VALUES = {
  name: "name",
  age: "age",
  address: "address",
};

export function getVal(key: string) {
  if (key in VALUES) {
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ name: string; age: string; address: string; }'.
//  No index signature with a parameter of type 'string' was found on type '{ name: string; age: string; address: string; }'.ts(7053)
    return VALUES[key];
  }

  return "";
}

The conditional is explicitly making the key be one of the keys in VALUES in the true branch. So why is typescript complaining? Wouldn't key in obj narrow key to be one of the keys in obj?

I understand I can do one of two things to fix it:

A/

const VALUES: { [index: string]: string } = {
  name: "name",
  age: "age",
  address: "address",
};

B/

export function getVal(key: keyof typeof VALUES) {
  ...
}

But what if I want to keep VALUES type constrained to those keys, and/or the key param remain as a string?

EDIT:

As T.J. Crowder pointed out in the comments, perhaps a better "fix" would be:

const VALUES = {
  name: "name",
  age: "age",
  address: "address",
};

function isValidKey(key: string): key is keyof typeof VALUES {
  return key in VALUES;
}

export function getVal(key: string) {
  if (isValidKey(key)) {
    return VALUES[key];
  }

  return "";
}

Upvotes: 5

Views: 1220

Answers (1)

jcalz
jcalz

Reputation: 327934

TypeScript supports narrowing via in operator, but currently a check like key in obj will only serve to narrow the type of obj and not the type of key. Traditionally this would only work when obj was of a union type, and then "k" in obj would filter the union to just those members with a known k key. TypeScript 4.9 added additional narrowing for non-union types in; so if obj is not a union, "k" in obj will narrow obj to a type where obj.x is an unknown property. Neither of these narrowings do anything to key given a key in obj check.

The reason this doesn't already happen is because it was never implemented. There is an open feature request at microsoft/TypeScript#43284 asking to narrow the type of key. It's marked as "Awaiting more feedback", so it is unlikely to get implemented unless there's more engagement on the issue, with 👍 and descriptions of use cases, and why the current workarounds are not acceptable.

Upvotes: 7

Related Questions