From the following Union type:
type Modifier =
| Date
| RangeModifier
| BeforeModifier
| AfterModifier
| BeforeAfterModifier
| DaysOfWeekModifier
| FunctionModifier
| undefined;
...I've built the following out of the type names:
const MODIFIER_NAMES = [
'undefined',
'Date',
'RangeModifier',
'BeforeModifier',
'AfterModifier',
'BeforeAfterModifier',
'DaysOfWeekModifier',
'FunctionModifier',
] as const;
type ModifierNamesTuple = typeof MODIFIER_NAMES;
type ModifierNames = ModifierNamesTuple[ number ];
I need to strictly map the names in ModifierNames
to their corresponding type in Modifier
, so that I can use those in a JS Map object. Something like...
const modifierMap = new Map<ModifierNames, Modifier>();
...but with the intended type safety between the key and its value. For example:
// For these modifiers (notice their type)...
const rangeModifier: Modifier = {
from: new Date(),
to: new Date()
}
const beforeModifier: Modifier = {
before: new Date()
}
// The following should be invalid
modifierMap.set('BeforeModifier', rangeModifier);
// While the following should be valid
modifierMap.set('RangeModifier', rangeModifier);
How can I achieve this?
type FunctionModifier = {
tag: 'FunctionModifier'
}
type DaysOfWeekModifier = {
tag: 'DaysOfWeekModifier'
}
type Dictionary = {
'Date': Date,
'DaysOfWeekModifier': DaysOfWeekModifier,
'FunctionModifier': FunctionModifier,
'undefined': undefined
}
// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type Values<T> = T[keyof T]
/**
* First step
*/
type HashMap =
{
[Prop in keyof Dictionary]: Map<Prop, Dictionary[Prop]>
}
/**
* Second step
*/
type UnionOfStates = Values<HashMap>
/**
* Third step
*/
type MapOverloading = UnionToIntersection<UnionOfStates>
const modifierMap: MapOverloading = new Map();
modifierMap.get('FunctionModifier') // FunctionModifier | undefined
modifierMap.set('DaysOfWeekModifier', { tag: 'DaysOfWeekModifier' }) // ok
modifierMap.set('DaysOfWeekModifier', { tag: 'invalid' }) // error
modifierMap.set('DaysOfWeekModifier', 42) // error
In order to make it work you need to overload modifierMap
. I mean, you need to create a union of all possible Map
states and then intersect them
UPDATE
If you want to call map.set
inside a function, you need then to infer function arguments
type FunctionModifier = {
tag: 'FunctionModifier'
}
type DaysOfWeekModifier = {
tag: 'DaysOfWeekModifier'
}
type Dictionary = {
'Date': Date,
'DaysOfWeekModifier': DaysOfWeekModifier,
'FunctionModifier': FunctionModifier,
'undefined': undefined
}
type Values<T> = T[keyof T]
const modifierMap = new Map<keyof Dictionary, Values<Dictionary>>();
const setter = <Key extends keyof Dictionary>(key: Key, value: Dictionary[Key]) => {
modifierMap.set(key, value);
}
setter('Date', new Date) // ok
setter('Date', 32)// error