Typescript template literal index signature

i’m working on a converter app and have been trying ~ for a while now ~ to successfuly type an object with template literals as an index signature.

What I want to do: type the formulas object in the code below, that would contain every formula to convert celsius to kelvin, kelvin to farenheit, etc.

I was able to type it like that :

type ConversionsOptions<T extends TemperatureUnit> = {
  key: ConversionCategoryType;
  items: Array<ConversionItem<T>>;
  formulas: {
    [U in `${T}_to_${T}`]: (n: number) => number;
  };
};

The object for the conversions looks like that :

const temperatureCategory: ConversionsOptions<
  'celsius' | 'farenheit' | 'kelvin'
> = {
  key: 'temperature',
  items: [
    {
      key: 'celsius',
    },
    {
      key: 'kelvin',
    },
    {
      key: 'farenheit',
    },
  ],
  formulas: {
    celsius_to_kelvin: (n: number) => n + 273.15,
    celsius_to_farenheit: (n: number) => n * (9 / 5) + 32,
    kelvin_to_farenheit: (n: number) => (n - 273.15) * (9 / 5) + 32,
    kelvin_to_celsius: (n: number) => n - 273.15,
    farenheit_to_kelvin: (n: number) => (n - 32) * (5 / 9) + 273.15,
    farenheit_to_celsius: (n: number) => (n - 32) * (5 / 9),
  },
};

So with this type, the formula’s object indexes are correct but it allows celsius_to_celsius, farenheit_to_farenheit and kelvin_to_kelvin to be in the object.

I tried to use Exclude utility type like this :

[U in `${T}_to_${Exclude<T, U>}`]: (n: number) => number;

in the index signature, but unfortunately, it doesn’t work.

Any ideas how this problem could be solved ?

>Solution :

If we use key remapping, we can use Exclude:

type ConversionsOptions<T extends TemperatureUnit> = {
  formulas:{
    [U in T as `${U}_to_${Exclude<T, U>}`]: (n: number) => number;
  };
};

Playground


We can make a type that gets the duplicates utilizing distributive conditional types:

type Duplicates<T extends string> = T extends T ? `${T}_to_${T}` : never;

and then simply omit the dupes from the mapped type:

type ConversionsOptions<T extends TemperatureUnit> = {
  formulas: Omit<{
    [U in `${T}_to_${T}`]: (n: number) => number;
  }, Duplicates<T>>;
};

Playground


You could also inline the type if you don’t want to create another type that’s only used once:

type ConversionsOptions<T extends TemperatureUnit> = {
  formulas: Omit<{
    [U in `${T}_to_${T}`]: (n: number) => number;
  }, T extends T ? `${T}_to_${T}` : never>;
};

Playground

Leave a Reply