What I'd like to do is to create an object which is a Record of a certain interface and have TypeScript be able to infer the keys based on what's in my object. I've tried a few things, none of which do exactly what I'm looking for.
interface Person {
firstName:string
}
const peopleObj = {
Jason: {
firstName: "Jason"
},
Tim: {
firstName: "Tim"
}
} as const;
console.log(peopleObj);
Here, if you look at peopleObj, TypeScript knows the exact keys because of the as const. The problem here is I'm not enforcing each object to be a Person. So I tried this next:
const peopleObj: Record<string, Person> = {
Jason: {
firstName: "Jason"
},
Tim: {
firstName: "Tim"
}
} as const;
Here, each object has to be a Person because of the Record defined, but TypeScript loses its ability to know all of the keys because now they are just string instead of the constants 'Jason' | 'Tim', and this is the crux of the issue. I know I could explicitly use 'Jason' | 'Tim' in place of my string type, but this is a fairly large object in real life and updating that type explicitly every time I add to it or remove from it is getting to be tedious.
Is there a way to have the best of both worlds, where I can have TypeScript infer the keys in the object just based solely on what's in the object? I have found a way, although it's not super clean and I feel like there's likely a better way:
interface Person {
firstName:string
}
type PeopleType<T extends string> = Record<T, Person>;
const peopleObj: Record<string, Person> = {
Jason: {
firstName: "Jason"
},
Tim: {
firstName: "Tim"
}
} as const;
const People:Record<keyof typeof peopleObj, Person> = peopleObj;
console.log(People.Jason);