I would like to create a type guard function which checks if specific keys in an object have e.g. a string
value. This is easy enough if I know the key names:
const data: object = { a: 'one', b: 'two', c: 'three' };
function hasStringKeyA(data: any): data is { a: string } {
return typeof data.a === 'string';
}
if (hasStringKeyA(data)) {
console.log(data.a);
}
But when I need to check for more keys, this gets messy:
if (hasStringKeyA(data) && hasStringKeyB(data)) {
console.log([ data.a, data.b ].join(', '));
}
What I would like to do instead:
if (hasStringKeys(data, ['a', 'b', 'c'])) {
console.log([ data.a, data.c, data.b ].join(', '));
}
But I cannot figure out how to write a type guard function which can be parametrized this way. Here’s a lousy attempt which does not work:
function hasStringKeys <K extends string[]> (data: any, keys: Keys): data is { [ k: typeof K ]: string } {
for (const key of keys) {
if (typeof data[key] !== 'string') {
return false;
}
}
return true;
}
Is this possible? How?
You are pretty close. I would use a type parameter to capture the string literal type of the items instead of the whole string array. And you need to use a mapped type instead of an index signature, you could write it yourself ({ [ k in K ]: string }
) but the predefined Record
type should work as well:
const data: object = { a: 'one', b: 'two', c: 'three' };
if (hasStringKeys(data, ['a', 'b', 'c'])) {
console.log([ data.a, data.c, data.b ].join(', '));
}
function hasStringKeys <K extends string> (data: any, keys: K[]): data is Record<K, string> {
for (const key of keys) {
if (typeof data[key] !== 'string') {
return false;
}
}
return true;
}