I'm trying to get TS generics to map to a new object. In short, I'm trying to convert:
{
key: { handler: () => string },
key2: { hander: () => number },
}
to:
{ key: string, key2: number }
full example:
type programOption = {
validator: () => unknown
}
type programConfig<T extends {[key: string]: programOption} = {}> = {
options: T,
handler: (data: mapProgramConfig<T>) => void,
}
type mapProgramConfig<T extends {[key: string]: programOption}> = {
[K in keyof T]: ReturnType<programOption['validator']>
}
type mapProgramConfigHardcoded<T> = {
fruit: string,
animal: number
}
class Program {
constructor (config: programConfig) {}
}
const foo = new Program({
options: {
'fruit': { validator: () => 'asdf' },
'animal': { validator: () => 42 },
},
handler: ({fruit, animal, thing}) => {
},
});
Exactly what I'm trying to do can be seen if you replace mapProgramConfig
with mapProgramConfigHardcoded
in the programConfig
type, but I can't seem to make it work in the generic case.
CodePudding user response:
Consider this solution:
type ProgramOption<T> = {
validator?: () => T
}
type Convert<Obj extends Record<string, ProgramOption<any>>> = {
[Prop in keyof Obj]: Obj[Prop]['validator'] extends () => infer Return ? Return : never
}
const program = <
Keys extends PropertyKey,
ValidatorValues extends string | number,
Options extends Record<Keys, ProgramOption<ValidatorValues>>,
Handler extends (data: Convert<Options>) => void,
>(data: { options: Options, handler: Handler },) => {
return data
}
const foo = program({
options: {
'fruit': { validator: () => 'string' },
'animal': { validator: () => 42 },
},
handler: (obj) => {
obj.animal // 42
obj.fruit // 'string'
}
});
In order to infer obj
argument in handler
property you need infer all nested keys and values.
Keys
- refers to fruit
and animal
keys of nested object
ValidatorValues
- refers to Validator
return type
Options
- refers to whole options
property
Handler
- refers to handler
accordingly.
I have used Convert
utility type to iterate through Options
type and grab all return types of validator
property
If you are interested in function arguments inference you can check my article
I have used program
function instead of Program
class because
Type parameters cannot appear on a constructor declaration
CodePudding user response:
You can define it like this
type programConfig<T extends Record<string, any>> = {
options: {
[K in keyof T]: {
validator: () => T[K]
}
},
handler: (data: T) => void, // ??? should be {optionKey: inferred return type}
}
class Program<T extends Record<string, any> = Record<string, any>> {
constructor(config: programConfig<T>) { }
}
and when you call it the generic will be inferred only with the necessary data (the key and the data type)
const foo: Program<{
fruit: string;
animal: number;
}>
CodePudding user response:
you just need to extend the generic defined on the programConfig
interface to the Program
class:
class Program<T extends {[key: string]: programOption} = {}> {
constructor (config: programConfig<T>) {}
}