TypeScript: check key in object / narrowing at runtime

Viewed 302

I have the following (almost) JavaScript code that works as intended:

const config = {
  a: 1,
  b: 2,
  default: 3
}

const getConfig = (key: string) => {
  return config[key] || config.default
}

However, in TypeScript gives me of course an error, because not any kind of string is a valid index of config.

But the following unfortunately doesn't work either:

const getConfig = (key: string) => {
  return key in config
    ? config[key]
    : config.default
}

See TypeScript playground.

Is there a way to construct some kind of type predicate or what would you recommend? Do I really have to do config as any? That would seem silly...

3 Answers

You can give a type to your config object - { [key:string]: number }

const config: { [key:string]: number } = {
  a: 1,
  b: 2,
  default: 3
}

And then you can get any value from the object by string key.

You can make this type a little more generic, for example

type Map<T> = {
  [key: string] : T
}

For index key you can use number as well.

You can use record to achieve that:

const config: Record<string, number> = {
  a: 1,
  b: 2,
  default: 3
}

const getConfig = (key: string) => {
  return key in config
    ? config[key]
    : config.default
}

getConfig('a')
getConfig('c')

Typescript Playground

Here is another solution where you will have the type inference.

Beware, I will be using the ugly as any but I am typing the function so it is not visible when you use the getConfig function.

type Config = (typeof config)['default'] // just number in this case

const getConfig = (key: string): Config => {
  return (config as any)[key] || config.default
}

Or for different config values:

const config = {
  a: 1,
  b: "yo",
  default: {
    something: true
  }
}

type Config = typeof config;

type ConfigKey = keyof Config;

const getConfig = <K extends string>(key: K): K extends ConfigKey ? Config[K] : Config['default'] => {
  return (config as any)[key]  || config.default
}

const ofTypeNumber = getConfig('a')
const ofTypeString = getConfig('b')
const ofTypeDefaultConfig = getConfig('c')
Related