Exploring a Useful Helper Function
const isKeyOf = <K extends PropertyKey, O extends object>(key: K, object: O): key is K & keyof O => key in object
The isKeyOf
function essentially narrows down the key type to determine if a key exists within an object
const obj = {
a: 1,
b: 2,
c: 3
}
const key: string = "a" // the type of key is string
if (isKeyOf(key, obj)) {
// the type of key would be narrowed to "a" | "b" | "c"
}
This approach works well unless the object is a union of types, like in this case:
const holder: Record<number, { a: number } | { b: number }> = {
1: { a: 1 },
2: { b: 2 }
}
// the type of obj becomes { a: number } | { b: number }
const obj = holder[1 as number]
const key: string = "a" // the type of key is string
if (isKeyOf(key, obj)) {
// the type of key turns out to be never!
// Interestingly, this branch does get executed and prints...
console.log(typeof key) // prints string
console.log(obj) // prints { a: 1 }
}
Further investigation revealed that the issue arises from the behavior of the keyof
operator with union types
type objType = { a: number } | { b: number }
type key = keyof objType // = never
In such situations, how can we construct a type guard that accurately validates this and assigns the correct type to the key?