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