Home > Back-end >  Type 'Key' cannot be used to index type 'Object' in TypeScript
Type 'Key' cannot be used to index type 'Object' in TypeScript

Time:04-30

I have some object:

const obj = { age: 25, name: "Michal", gender: "Male" }
type Object = typeof obj

Then, I have type helper, that will return type of the specified property from this object:

type Value<Key extends keyof Object> = Object[Key]

Value<"age"> // number

What I should do, if Key in Value could be also void? Something like this:

type Value<Key extends void | keyof Object> = Key extends void ? never : Object[Key]

But it doesn't work and error appears:

Type 'Key' cannot be used to index type 'Object'

How can I fix it?

CodePudding user response:

This is currently a design limitation or missing feature of TypeScript; see microsoft/TypeScript#48710 and microsoft/TypeScript#26240 (and probably others) for relevant issues.

Conditional types of the form T extends U ? TrueBranch<T> : FalseBranch<T> can narrow the type T to the constraint U in the true branch, becoming something like TrueBranch<T & U>. But there is no narrowing whatsoever in the false branch. TypeScript doesn't have negated types of the form not U (this was implemented in microsoft/TypeScript#29317 but was never released), so there's no way to express FalseBranch<T & not U> in the general case. You just get FalseBranch<T> for the original, un-narrowed T. In specific cases where both T and U are unions of literal types, then you could filter T to something like Exclude<T, U>, but this has not yet been implemented.


Luckily, there are workarounds. In cases like yours, you only really need Key to be narrowed to keyof Object; you don't care about narrowing to void, because you produce never if Key is void. You can simply reverse the sense of the check so that the true branch and the false branch are in different places:

type Value<Key extends void | keyof Object> = 
  Key extends keyof Object ? Object[Key] : never; // okay

Of course, in some situations, you really need to narrow both in the true branch and in the false branch, so you can't simply swap what you're checking:

type Foo = { x: string, y: number };
type Bar = { a: string, b: number };

type BazBad1<K extends keyof Foo | keyof Bar> =
  K extends keyof Foo ? Foo[K] : Bar[K]; // error!

type BazBad2<K extends keyof Foo | keyof Bar> =
  K extends keyof Bar ? Bar[K] : Foo[K]; // error!

In these cases you can add a redundant check to get the desired behavior:

type Baz<K extends keyof Foo | keyof Bar> =
  K extends keyof Foo ? Foo[K] : 
  K extends keyof Bar ? Bar[K] : 
  never; // okay

Playground link to code

  • Related