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;
};
};
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>>;
};
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>;
};