This is because TypeScript sees col.name as a string, and the Cat interface only contains two possible keys (not any string as keys). In theory, col.name can return any string that is not present as a key in the object cat.
Since you know that col.name must be a keyof the Cat interface, you can hint TypeScript as such:
// Tell TS that col.name must be a keyof the Cat interface
cat[col.name as keyof Cat]
Therefore, updating your code like this will cause TypeScript to recognize that col.name indeed returns a valid key in the Cat interface:
cats.map(cat => {
cols.map(col => {
console.log(`${col.title} - ${cat[col.name as keyof Cat]}`);
});
});
See proof-of-concept example on TypeScript Playground.
Pro-tip: let's say that you don't have an explicit interface Cat but only have the cat object, you can also do:
// Tell TS that col.name must be a keyof the typeof `cat` object
cat[col.name as keyof typeof cat]
An alternative way is to use enum for the properties in the Cat interface, so you don't have to manually cast it: since TypeScript can infer the type as such. This method is considerably more verbose at the expanse of not needed to manually cast the type of col.name (see Playground example here):
// Declare an enum for properties in `Cat` interface
enum CatProperty {
FUR = 'fur',
FOOD = 'food',
}
Then, when defining an array for your cols, you will need to use enums:
const cols: Array<Cols> = [
{
name: CatProperty.FUR,
title: 'Fur colour'
}
];
This should result in TypeScript being able to recognize that col.name must be a value of fur | food, which all exists in the Cat interface, so no casting is needed:
cats.map(cat => {
cols.map(col => {
console.log(`${col.title} - ${cat[col.name]}`);
});
});