Search code examples
typescript

Define TypeScript type of object whose keys are constants


My question is similar to Define Typescript interface with using constant names as keys but is a little bit different.

I start off with the same type of object whose keys are constants

const KEYS = {
  KEY1: 'hello',
  KEY2: 'world'
} as const;

But instead of strings, I have objects as values. So in my code I want to know what type of object is in that key. (All of the object types are the same.) So for example, I really want something like:

const KEYS : {const: string} {
  KEY1: 'hello',
  KEY2: 'world'
} as const;

Is there some way to do this? I keep getting error ts2353 because the const doesn't appear properly. If possible, I would like a 1 line solution (aka, what do I need to replace const: string with to make this work) because I have almost 200 keys, so I'd prefer not rewriting each const individually as a type.

According to jcalz, it sounds like I want something like an "index signature"? I tried a few different types and nothing seems to work so I'm not sure what I'm doing wrong.


Solution

  • It sounds like you want an index signature, since "you don't know all the names of a type's properties ahead of time, but you do know the shape of the values." Instead of {const: string} you're looking for {[k: string]: string}, which means "every property with key k of type string will have a value of type string". You can also write that as Record<string, string> using the Record utility type.

    Given that you're using a const assertion you presumably don't want to make the compiler actually forget about the known keys KEY1 and KEY2 or the literal types "hello" and "world". So while you could write

    const KEYS: { [k: string]: string } = {
      KEY1: 'hello',
      KEY2: 'world'
    } as const;
    

    and annotate KEYS as having that index signature type, it's not what you want. Instead it looks like you just want to make sure that KEYS satisfies that index signature type without widening it to that type. If so then you can use the satisfies operator:

    const KEYS = {
      KEY1: 'hello',
      KEY2: 'world'
    } as const satisfies { [k: string]: string };
    

    That compiles without changing the inferred type of KEYS, but will still catch an error if you mistakenly make one of the properties have a bad value:

    const KEYS = {
      KEY1: 'hello',
      KEY2: 123 // error!
      //~~ <-- Type 'number' is not assignable to type 'string'.
    } as const satisfies { [k: string]: string };
    

    Playground link to code