Reputation: 5059
Example 1:
const myGroup = {
Alice: { age: 5 },
Bob: { age: 6 },
}
function getAge(name: keyof typeof myGroup) {
return myGroup[name].age;
}
getAge('Charlie'); // error: name must be 'Alice' | 'Bob'
ā This is what I want, however, if I type everything, it'll no longer work:
Example 2
interface People {
age: number;
}
interface PeopleGroup {
[name:string]: People;
}
const myGroup: PeopleGroup = {
Alice: { age: 5 },
Bob: { age: 6 },
}
function getAge(name: keyof typeof myGroup) {
return myGroup[name].age;
}
getAge('Charlie'); // no error, name has type of string
I know I can solve it by using enum
or union type
, but that requires editing more than one place when adding people. In my real life project, myGroup
is a big hard-coded dictionary with complicated structure, and I want to enjoy both typechecking when hard-coding it, and typechecking when querying it. Is there a dry way to do it?
Upvotes: 3
Views: 2197
Reputation: 20132
Ok so the issue is that when you say something is Record<string, People>
then any string key is correct and type of myGroup
is not narrowed to keys which are really there. In order to achieve type safe in two places:
We need to introduce value constructor:
function makeGroup<T extends PeopleGroup>(input: T) {
return input;
}
// using
const myGroup = makeGroup({
Alice: { age: 5 },
Bob: { age: 6 },
});
What it does is it takes something which match the interface PeopleGroup
- its a first type safe element, it means that argument needs to be PeopleGroup
and returns narrowed type. The second is very important, using generic type says TS that we want to infer exact type of the argument. In result as output we have exactly type of the argument.
Full code:
interface People {
age: number;
}
interface PeopleGroup {
[name:string]: People;
}
// value constructor
function makeGroup<T extends PeopleGroup>(input: T) {
return input;
}
// correct creating of group - type safe for PeopleGroup
const myGroup = makeGroup({
Alice: { age: 5 },
Bob: { age: 6 },
});
const myGroupError = makeGroup({
Alice: { age: 5 },
Bob: { age: 'a' }, // error as it should be š
});
function getAge(name: keyof typeof myGroup) {
return myGroup[name].age;
}
getAge('Alice') // ok !
getAge('Charlie'); // error as it should be š
Upvotes: 3