Search code examples
javascripttypescripttypescript-generics

Object with only string or number keys in typescript, and not symbol


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...


Solution

  • 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-symbols with string to tell the compiler that you only want to use the subset which are strings:

    interface EventHandler<
      Method extends 'create' | 'update',
      Models extends object
    > {
      on: `${string & keyof Models}.${Method}`, // okay
      main(): void
    }
    

    Playground link to code