I am encountering an issue trying to make typescript recognise the keys of a javascript object for me, while enforcing each key's value type because I want to create a typeof the keys of the object, so I can't just create a regular type MyObject = { [key: string]: <insert type> }.
Imagine an object myobject where I extract the keys of it like:
const myobject = {
foo: {},
bar: {}
};
type MyObjectKeys = keyof typeof myobject; // 'foo' | 'bar'
How can I add type definitions to the values of the keys, while still being able to extract/inherit the definitions of the keys? If I do something like this, then I will no longer be able to extract the exact keys of the object, but only the type (string):
type MyObject = { [key: string]: { value: boolean }}
const myobject = {
foo: { value: true },
bar: { value: false }
};
type MyObjectKeys = keyof typeof myobject; // string
I figured that I could achieve this by creating a helper function like:
function enforceObjectType<T extends MyObject>(o: T) {
return Object.freeze(o);
}
const myobject = enforceObjectType({
foo: {},
bar: {}
});
But I'd prefer to define a clear type for it, without having to pollute the code, writing functions only related to types. Is there a way to allow a set of strings as keys of a type without repetition?
The purpose of this is to get TypeScript to help pointing out the right object keys like (the real usage is a bit more complex, so I hope this describes it well enough):
type MyObjectKeys = keyof typeof myobject; // string
function getMyObjectValue(key: MyObjectKeys) {
const objectValue = myobject[key];
}
// suggest all available keys, while showing an error for unknown keys
getMyObjectValue('foo'); // success
getMyObjectValue('bar'); // success
getMyObjectValue('unknown'); // failure
Wrap up: I want to define an object as const (in fact with Object.freeze) and be able to:
- Extract the exact keys of the object (without having to type a definition of each key).
- Define the type of each key, without overwriting the keys to
stringinstead of what they are - like'foo' | 'bar'.
Complete example
type GameObj = { skillLevel: EnumOfSkillLevels }; // ADD to each key.
const GAMES_OBJECT = Object.freeze({
wow: { skillLevel: 'average' },
csgo: { skillLevel 'good' }
)};
type GamesObjectKeys = keyof typeof GAMES_OBJECT;
function getSkillLevel(key: GamesObjectKeys) {
return GAMES_OBJECT[key]
}
getSkillLevel('wow') // Get the actual wow object
getSkillLevel('unknown') // Get an error because the object does not contain this.
In accordance to above, I can't do the following because that will overwrite the known keys to just any string:
type GameObj = { [key: string]: skillLevel: EnumOfSkillLevels };
const GAMES_OBJECT: GameObj = Object.freeze({
wow: { skillLevel: 'average' },
csgo: { skillLevel 'good' }
)};
type GamesObjectKeys = keyof typeof GAMES_OBJECT;
function getSkillLevel(key: GamesObjectKeys) {
return GAMES_OBJECT[key]
}
getSkillLevel('wow') // Does return wow object, but gives me no real-time TS help
getSkillLevel('unknown') // Does not give me a TS error
Another example: See this gist for example and copy it to typescript playground if you want to change the code
myObjectas constant?myobjectwhile still being able to "inherit" the keys dynamically.GameObjand thegetSkillLevelto keeps just accepting the right keys, not just string, right?