I am looking to force an object type to only contain string keys.
I tried a lot of things, here is my best bet:
type StrKeyObj<Obj extends Record<string, any> = Record<string, any>> = { [K in keyof Obj]: K extends string ? K : never }
export interface EventHandler<
Method extends 'create' | 'update',
// I wish this type can be forced to an object with only string keys
Models extends StrKeyObj
> {
// so that I can use this pattern without an error
on: `${keyof Models}.${Method}`, // 🔴 Type 'symbol' is not assignable to type 'string...'
main(): void
}
Here is my typescript playground
It seems like an easy question but I didn't find any answers right now...
You can't really prohibit keys in a TypeScript object type. The closest you can get is to say that keys you don't like must have values of the never
type, which is close:
type NoSymbolKeys = {
[k: string]: unknown,
[k: symbol]: never;
}
const sym = Symbol();
let x: NoSymbolKeys;
x = { a: 1, b: 2 };
x = { a: 1, [sym]: 2 }; // error
But that's not really what you're looking for. Your use case is to use keyof
in a template literal type and that still fails because you cannot ever be certain there's no symbol
keys in some generic object type:
type Oops<T extends NoSymbolKeys> = `${keyof T}` // still error!
Instead, the standard approach here is to intersect your thing-which-might-have-symbol
s with string
to tell the compiler that you only want to use the subset which are string
s:
interface EventHandler<
Method extends 'create' | 'update',
Models extends object
> {
on: `${string & keyof Models}.${Method}`, // okay
main(): void
}