Home > Enterprise >  Generic response type based on input type
Generic response type based on input type

Time:07-12

I try to write types for simple function fun.

Here are some interfaces for input and response:

interface Input {
  test1?: true
  test2?: true
  test3?: true
}
interface Res {
  test1?: string
  test2?: string
  test3?: string
}

and the function itself

fun(input: Input): Res {
    const res: Res = {}
    if (input.test1) {
      res.test1 = 'jupi'
    }
    if (input.test2) {
      res.test2 = 'jupiX2'
    }
    if (input.test3) {
      res.test3 = 'jupiX3'
    }
    return res
  }

With current types, the response of the function fun is garanted to be Res (as explicitly typed) regardless of the provided input. On the other hand, we can see that the implementation of fun will return values for each key that is passed in input.

The proposed solution for connecting input and response types is Function Overloads.

My example would look like this:

fun(input: RequireMany<Input, 'test1'>): Pick<Res, 'test1'>
fun(input: RequireMany<Input, 'test2'>): Pick<Res, 'test2'>
fun(input: RequireMany<Input, 'test3'>): Pick<Res, 'test3'>
fun(input: RequireMany<Input, 'test1' | 'test2'>): Pick<Res, 'test1' | 'test2'>
fun(input: RequireMany<Input, 'test1' | 'test3'>): Pick<Res, 'test1' | 'test3'>
fun(input: RequireMany<Input, 'test2' | 'test3'>): Pick<Res, 'test2' | 'test3'>
fun(input: Required<Input>): Required<Res>
fun(input: Input): Res {
  const res: Res = {}
  if (input.test1) {
    res.test1 = 'jupi'
  }
  if (input.test2) {
    res.test2 = 'jupiX2'
  }
  if (input.test3) {
    res.test3 = 'differentText'
  }
  return res
}

where RequireMany is

type RequireMany<T, Keys extends keyof T = keyof T> = Pick<T, Extract<keyof T, Keys>>

I plan to have at least 7 keys in my input, so here comes my question:

Is there a better way (some generic?) to type the fun than a lot of Function Overloads?

CodePudding user response:

You can perhaps do something like this with generics:

Typescript Playground Link


type Input = {
  test1?: boolean | undefined
  test2?: boolean | undefined
  test3?: boolean | undefined
}

type Res<T extends Input> = {
  test1: T['test1'] extends boolean ? string : undefined // another option here is to use 'never' instead of 'undefined' depending on your use case
  test2: T['test2'] extends boolean ? string : undefined
  test3: T['test3'] extends boolean ? string : undefined
}

function fun<T extends Input>(input: T): Res<T> {
  return {
    ...('test1' in input ? { test1: 'jupi' } : {}),
    ...('test2' in input ? { test2: 'jupiX2' } : {}),
    ...('test3' in input ? { test3: 'jupiX3' } : {}),
  } as Res<T>
}

console.log(fun({ test1: true }).test1) // hovering over test1 shows that it's resolved to 'string'
console.log(fun({ test1: true }).test2) // test2 is 'undefined'
console.log(fun({ test1: true }).test3) // test3 is 'undefined'

  • Related