TypeScript: Exact key value pairs

If you create an object like this, although the keys are correctly inferred, all of the values will be of type string.

const obj = {
  FOO: 'FOO', // FOO: string
  BAR: 'BAR', // BAR: string
};

This means that even though you are only allowing access to certain keys, you cannot ensure that the values exactly match these keys.

You can easily fix this by defining a type for the key values, which we can use to enforce key value pairs that match.

type KeyValue = 'FOO' | 'BAR';

You can then use this to define the exact keys and values in an object.

const exactObj: {[K in KeyValue]: K} = {
  FOO: 'FOO', // FOO: 'FOO'
  BAR: 'BAR', // BAR: 'BAR'
};

We can also define this inline so that we don’t need the extra type definition.

const exactObj: {[K in 'FOO' | 'BAR']: K} = {
  FOO: 'FOO', // FOO: 'FOO'
  BAR: 'BAR', // BAR: 'BAR'
};

Or, more sensibly, we could create a generic type that we can use to define multiple key value pairs.

type KeyValuePairs<T extends string> = {[K in T]: K};

const exactObj: KeyValuePairs<'FOO' | 'BAR'> = {
  FOO: 'FOO', // FOO: 'FOO'
  BAR: 'BAR', // BAR: 'BAR'
};

Again, we could also have 'FOO' | 'BAR' defined as its own type, as we’ll likely need to refer to these values elsewhere.