Let's start by analyzing the issues with the current approach, specifically the types assigned to the two arguments.
functions: {[P in keyof A]: (a: A[P]) => B[P]},
data: {[P in keyof A]: A[P]}
It seems like you intend for the type A[P]
to represent the value types of the object A
. However, since there are no constraints on the type A
, the values are inferred as unknown
due to lack of specificity.
You can manually specify generic parameters when calling the apply
function, like this:
type A = {} // What goes here?
type B = {} // What goes here?
const result = apply<A, B>(functions, data)
The challenge lies in determining a suitable type for A
in this scenario. It appears that the only valid type would be similar to what you currently have:
{foo: unknown, bar: unknown, baz: unknown}
.
Instead of utilizing a mapped type for the arguments as shown above, why not incorporate these constraints into the generic signature itself? You seem to desire the functions
parameter to consist of single-argument functions. Let's enforce that requirement directly in the generic A
:
A extends Record<string, (a: any) => any>
As for B
, representing the data type, it should align with the argument type of each corresponding function in
A</code. We can establish this constraint within the generic signature as well:</p>
<pre><code>B extends {[P in keyof A]: Parameters<A[P]>[0]}
Lastly, for the return type, which is another object mirroring the return types of A
's functions:
{[P in keyof A]: ReturnType<A[P]>
Combining all these aspects:
function apply<A extends Record<string, (a: any) => any>, B extends {[P in keyof A]: Parameters<A[P]>[0]}>(
functions: A,
data: B
): {[P in keyof A]: ReturnType<A[P]> {
const result = {} as {[P in keyof A]: B[P]}
const keys = Object.keys(functions) as (keyof A)[]
for(const key of keys)
result[key] = functions[key](data[key])
return result
}
const functions = {
foo: (a: string) => a.length,
bar: (a: number) => a === 42,
baz: (a: boolean) => a ? 'true' : 'false'
}
const data = {foo: 'foo', bar: 42, baz: true}
const result = apply(functions, data)
// The type of `result` is inferred as:
// {
// foo: number;
// bar: boolean;
// baz: "true" | "false";
// }