Home > front end >  How to dynamically infer type in Typescript?
How to dynamically infer type in Typescript?

Time:07-02

How can I make the compiler understand that the return type of the parse method is linked to the input of the serialize method and it is dynamic?

type QUESTION = {
  parse: (value: string) => any
  serialize: (value: ReturnType<QUESTION['parse']>) => string
}

const idsQuestions: QUESTION[] = [
  {
    parse: (input) => input.split('\n'),
    serialize: (ids) => ids.join('\n') // expect ids to be interpreted as string[]
  },
  {
    parse: (input) => input.split('\n').reduce((acc, id) => ({
      ...acc,
      [id]: true,
    }), {}),
    serialize: (ids) => Object.keys(ids).join('\n') // expect ids to be interpreted as { [string] => boolean }
  }
]

// expect this to throw an error on build
const questionWithError: QUESTION = {
  parse: () => false, // return type is boolean
  serialize: (ids) => ids.join('\n') // but ids is not interpreted as boolean, so compiler doens't give an error
}

I've tried some uses of generics and unknown, but could not make it work. Tried to use infer also, but could not understand exactly how to work with it

I could have something hardcoded like this, but I wanted it to be dynamic

type QUESTION<T> = {
  parse: (value: string) => T
  serialize: (value: T) => string
}

const idsQuestions: [
  QUESTION<string[]>,
  QUESTION<{ [k: string]: boolean }>,
] = [
  {
    parse: (input) => input.split('\n'),
    serialize: (ids) => ids.join('\n') // expect ids to be interpreted as string[]
  },
  {
    parse: (input) => input.split('\n').reduce((acc, id) => ({
      ...acc,
      [id]: true,
    }), {}),
    serialize: (ids) => Object.keys(ids).join('\n') // expect ids to be interpreted as { [string] => boolean }
  }
]

Playground link

CodePudding user response:

You need to use genric-type.

With this feature, you can tie between Types dynamically

interface Question<T>  {
  parse: (value: string) => T
  serialize: (value: T) => string
}

const idsQuestion: Question<string[]> = {
  parse: (input) => input.split('\n'),
  serialize: (ids) => ids.join('\n') // expect ids to be interpreted as string[]
}

// expect this to throw an error on build
const questionWithError: Question<boolean> = {
  parse: () => false, // return type is boolean
  serialize: (ids) => ids.join('\n') // the transpiler will show an error
}

CodePudding user response:

I don't think what you're trying to achieve is doable without some helper functions or making it excessively verbose. Make a function that could help determine what T should be.

type QUESTION<T> = {
  parse: (value: string) => T
  serialize: (value: T) => string
}

function QUESTION<T>(parse: (value: string) => T, serialize: (value: T) => string): QUESTION<T> {
  return { parse, serialize };
}

const idsQuestions = [
  QUESTION(
    (input) => input.split('\n'),
    (ids) => ids.join('\n') // expect ids to be interpreted as string[]
  ),
  QUESTION(
    (input) => input.split('\n').reduce((acc, id) => ({
      ...acc,
      [id]: true,
    }), {} as Record<string, boolean>),
    (ids) => Object.keys(ids).join('\n') // expect ids to be interpreted as { [string] => boolean }
  )
] as const;
  • Related