Home > Mobile >  TypeScript - Typing an object map of functions with exclusive parameters
TypeScript - Typing an object map of functions with exclusive parameters

Time:12-22

Here is an object where I keep functions by key:

export const questSetHelper: { [key in QuestSetHelper]: (player: Player, payload: FlagGender | FlagOrigin | FlagAdventure) => void } = {
  'setGender': setGender,
  'setOriginCircumstance': setOriginCircumstance,
  'setAdventureCircumstance': setAdventureCircumstance
}

I keep them as strings for easy handling from front-end to back-end passing strings that are used to determine which function to run. Each function handles only one of the payload types.

For example:

export const setOriginCircumstance = (player: Player, payload: FlagOrigin): void => {
  if (player.quests['intro']?.flags)
    player.quests['intro'].flags['origin_circumstance'] = payload
  else
    throw new Error('Quest: intro - failed setOriginCircumstance')
}

When all these were typed to string, I had no type errors of course, but I want to have each of these function parameters explicitly typed.

PROBLEM Obviously each function can ultimately handle ONE of the questSetHelper types of payload, but I want to assume that each function will be called correctly and passed the correct type (i.e. FlagGender payload will always go to setGender).

Is there a better way to type this without changing the structure?

CodePudding user response:

I believe there are 2 options:

Rely on type inference

This is a minor change to your code

export const setGender = (player: Player, payload: FlagGender): void => {}
export const setOriginCircumstance = (player: Player, payload: FlagOrigin): void => {}
export const setAdventureCircumstance = (player: Player, payload: FlagAdventure): void => {}

export const questSetHelper = {
  'setGender': setGender,
  'setOriginCircumstance': setOriginCircumstance,
  'setAdventureCircumstance': setAdventureCircumstance
}

Playground - inference

Use explicit typing to ensure all setters have correct types

In this approach, you start with defining QuestFlags. After that, you use Key Remapping in Mapped Types to generate setters with appropriate names:

interface QuestFlags {
  gender: FlagGender
  originCircumstance: FlagOrigin
  adventureCircumstance: FlagAdventure
}

type QuestFlagsSetters = {
    [K in keyof QuestFlags & string as `set${Capitalize<K>}`]: (player: Player, payload: QuestFlags[K]) => void
};

export const questSetHelper: QuestFlagsSetters = {
  'setGender': setGender,
  'setOriginCircumstance': setOriginCircumstance,
  'setAdventureCircumstance': setAdventureCircumstance
}

Playground - Key remapping

I see you use snake case for property names but camel case for setters. This is easy to achieve as well:

interface QuestFlags {
  gender: FlagGender
  origin_circumstance: FlagOrigin
  adventure_circumstance: FlagAdventure
}

type SnakeCaseToPascalCase<S extends string> =
    S extends `${infer FirstWord}_${infer Rest}` ?
        `${Capitalize<Lowercase<FirstWord>>}${SnakeCaseToPascalCase<Rest>}` :
        Capitalize<Lowercase<S>>;


type QuestFlagsSetters = {
    [K in keyof QuestFlags & string as `set${SnakeCaseToPascalCase<K>}`]: (player: Player, payload: QuestFlags[K]) => void
};

export const questSetHelper: QuestFlagsSetters = {
  'setGender': setGender,
  'setOriginCircumstance': setOriginCircumstance,
  'setAdventureCircumstance': setAdventureCircumstance
}

Playground - Key remapping with snake_case changed to camelCase

  • Related