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:
string
instead of what they are - like 'foo' | 'bar'
.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
Use an index signature to define a key-value pair in TypeScript, e.g. const employee: { [key: string]: string | number } = {} . An index signature is used when we don't know all the names of a type's keys ahead of time, but we know the shape of their values. Copied!
Use an index signature to define a type for an object with dynamic keys, e.g. [key: string]: string; . Index signatures are used when we don't know all of the names of a type's properties ahead of time, but know the shape of the values.
To dynamically access an object's property: Use keyof typeof obj as the type of the dynamic key, e.g. type ObjectKey = keyof typeof obj; . Use bracket notation to access the object's property, e.g. obj[myVar] .
Use the keyof typeof syntax to create a type from an object's keys, e.g. type Keys = keyof typeof person . The keyof typeof syntax returns a type that represents all of the object's keys as strings. index.ts. Copied!
While I have not found a way to completely avoid creating a javascript function to solve this (also told that might not be possible at all, at this moment), I have found what I believe is an acceptable solution:
type GameInfo = { [key: string]: { skillLevel: 'good' | 'average' | 'bad' }}
type EnforceObjectType<T> = <V extends T>(v: V) => V;
const enforceObjectType: EnforceObjectType<GameInfo> = v => v;
const GAMES2 = enforceObjectType({
CSGO: {
skillLevel: 'good',
},
WOW: {
skillLevel: 'average'
},
DOTA: {
// Compile error - missing property skillLevel
}
});
type GameKey2 = keyof typeof GAMES2;
function getGameInfo2(key: GameKey2) {
return GAMES2[key];
}
getGameInfo2('WOW');
getGameInfo2('CSGO');
getGameInfo2('unknown') // COMPILE ERROR HERE
This way we get:
I have updated my Gist to include this example and you might be able to see it in practice on typescript playground.
Hope this help you now:
enum EnumOfSkillLevels {
Average = 'average',
Good = 'good'
}
type GameObject<T extends { [key: string]: {skillLevel: EnumOfSkillLevels} }> = {
[key in keyof T ]: {skillLevel: EnumOfSkillLevels}
}
const GAMES_OBJECT = {
wow: { skillLevel: EnumOfSkillLevels.Average },
csgo: { skillLevel: EnumOfSkillLevels.Good },
lol: { skillLevel: EnumOfSkillLevels.Good }
} as const;
function getSkillLevel(key: keyof GameObject<typeof GAMES_OBJECT>) {
return GAMES_OBJECT[key]
}
getSkillLevel('wow') // Does return wow object, but gives me no real-time TS help
getSkillLevel('lol') // Does return wow object, but gives me no real-time TS help
getSkillLevel('unknown') // Does give me a TS error
Playground link.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With