1

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?

Arnie
  • 212
  • 1
  • 2
  • 11
  • pls share reproducable example – captain-yossarian from Ukraine Sep 09 '21 at 12:38
  • @captain-yossarian What do you mean? That IS a **reproducible** example. It is in fact the code I'm working with. The only difference is the interface definition for each of the union types in Modifier. But that is verbose and not required for an answer. – Arnie Sep 09 '21 at 12:51

1 Answers1

0
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

Playground

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
  • 1
    Thank you for your time, this worked. In fact, it worked so well that now I don't need MODIFIER_NAMES, ModifierNamesTuple and ModifierNames any longer. The dictionary is enough for typescript to understand what the Map.set() arguments should be. I suggest you edit your answer to remove them, since they're no longer needed. – Arnie Sep 09 '21 at 14:10
  • Now I'm having problems declaring a function that admits the map's key and value, so that it can call map.set(k, v) in its block of code. For example: function setter(key, value) { map.set(key, value); } How would you suggest I go about declaring the arguments in the setter function? – Arnie Sep 09 '21 at 16:15