Using TypeScript's in
keyword allows us to check if an object contains a specific key in a type-safe manner when the key is defined as a string literal:
function guardHasTest <Data extends object> (
value: Data
): Data & Record<'test', unknown> {
if (!('test' in value)) {
throw new Error('Missing key')
},
return value
}
However, if the key is dynamic and not a string literal, the same approach does not narrow down the type:
function guardHasKey <Data extends object, Key extends string> (
value: Data,
key: Key
): Data & Record<Key, unknown> {
if (!(key in value)) {
throw new Error('Missing key')
}
return value
// Type 'Data' is not assignable to type 'Data & Record<Key, unknown>'.
Type 'object' is not assignable to type 'Data & Record<Key, unknown>'.
Type 'object' is not assignable to type 'Data'.
'object' is assignable to the constraint of type 'Data', but 'Data' could be instantiated with a different subtype of constraint 'object'.
Type 'Data' is not assignable to type 'Record<Key, unknown>'.
Type 'object' is not assignable to type 'Record<Key, unknown>'.ts(2322)
}
Avoiding custom type guards is preferred due to their lack of type safety in logic:
export function isKeyOf<T, const Key extends string>(
obj: T,
key: Key
): obj is T & Record<Key, unknown> {
return true // No error, even though the logic is not correct
}
The challenge is how to create a function that can dynamically verify if an object contains a key in a completely type-safe manner?