'Create dynamic optional parameter for inferred generic

I have an object that is shaped like this:

{
  first: (state: S, payload: boolean) => { payload: boolean, type: string },
  second: (state: S) => { payload: undefined, type: string },
}

And I'm generating an object where the state property is taken out. So it looks like this:

{
  first: (payload: boolean) => { payload: boolean, type: string },
  second: () => { payload: undefined, type: string },
}

The returned object is typed like this:

{ [K in keyof T]: { (payload: Parameters<T[K]>[1]): { payload: Parameters<T[K]>[1]; type: K; }; name: K; } }

Where Parameters<T[K]>[1] can clearly be undefined (as in the second case above).

This mostly works fine. I get the correct type inference of the function names, the parameters, and the return types, EXCEPT if the payload is undefined. It still wants me to specify it. For example:

first(true); // works, no errors
first('true'); // fails because it's the wrong type. Excellent

second(undefined) // works, no errors
second(); // errors

The error is:

(property) second: (payload: undefined) => {
    payload: undefined;
    type: "second";
}
Expected 1 arguments, but got 0. [ts(2554)]

So the question is how can I make this optional, but only when the payload is not provided? If I add (payload?: Parameters<T[K]>[1]) in the return type then all the functions have this property as optional, and if I add (payload: Parameters<T[K]>[1] | undefined) - I still get the same result as before.



Solution 1:[1]

The answer was to create a conditional function declaration in the return type, like this:

{ [K in keyof T]: Parameters<T[K]>[1] extends undefined ?
  { (): { payload: Parameters<T[K]>[1]; type: K; }; name: K; } :
  { (payload: Parameters<T[K]>[1]): { payload: Parameters<T[K]>[1]; type: K; }; name: K; }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1