Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Typescript mapped generics

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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.

ts playground link

>Solution :

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'
  }
});

Playground

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

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading