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:
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'