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