Let's build an example record over some properties.
type HumanProp =
| "weight"
| "height"
| "age"
type Human = Record<HumanProp, number>;
const alice: Human = {
age: 31,
height: 176,
weight: 47
};
For each property, I also want add a human-readable label:
const humanPropLabels: Readonly<Record<HumanProp, string>> = {
weight: "Weight (kg)",
height: "Height (cm)",
age: "Age (full years)"
};
Now, using this Record type and defined labels, I want to iterate over two record with the same key types.
function describe(human: Human): string {
let lines: string[] = [];
for (const key in human) {
lines.push(`${humanPropLabels[key]}: ${human[key]}`);
}
return lines.join("\n");
}
However, I'm getting an error:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Readonly<Record<HumanProp, string>>'.
No index signature with a parameter of type 'string' was found on type 'Readonly<Record<HumanProp, string>>'.
How can I implement this functionality in Typescript properly?
To clarify, the solution I'm looking for, regardless of whether it will use Record type, plain objects, types, classes, interfaces or something else, should have following properties:
When I want to define a new property, I only need to do it in one place (like in HumanProp above), and don't repeat myself.
After I define a new property, all the places where I should add a new value for this property, like when I'm creating
alice, orhumanPropLabels, light up type errors at compilation time, not in runtime errors.Code that iterates over all properties, like
describefunction, should remain unchanged when I create a new property.
Is it even possible to implement something like that with Typescript's type system?