The getStructToResponse
function starts with an object of map functions, so the type of that object should be the generic and we'll work backwards from that to the input and output object types.
We define a general Mapper<In, Out>
as an object with a property toResponse
that maps a value from the In
type to the Out
type:
type Mapper<In, Out> = {
toResponse: (value: In) => Out
}
Given an object whose values are Mapper
, we can work backwards to the input and output of the object that it maps:
type InputsFromMap<M> = {
[K in keyof M]: M[K] extends Mapper<infer In, any> ? In : never;
}
type OutputsFromMap<M> = {
[K in keyof M]: M[K] extends Mapper<any, infer Out> ? Out : never;
}
Our function is generic depending on a MapObj
, which we say extends Record<keyof any, Mapper<any, any>
so we know that every property is a Mapper
.
It returns an object with a toResponse
function, so it returns a Mapper
. We use those inferred types to determine that we are returning Mapper<InputsFromMap<MapObj>, OutputsFromMap<MapObj>>
.
We don't need to type objectOfValues
because it is already known to be InputsFromMap<MapObj>
from our function return. We do need to set an initial type for var result = {} as OutputsFromMap<MapObj>
or else we won't be able to assign values to it because the key type won't be known. We also need to specify inside the forEach
that the type for the key
is keyof MapObj
and not just string
.
const getStructToResponse = <MapObj extends Record<keyof any, Mapper<any, any>>>(
objectOfFunctions: MapObj
): Mapper<InputsFromMap<MapObj>, OutputsFromMap<MapObj>> => ({
toResponse: (objectOfValues) => {
var result = {} as OutputsFromMap<MapObj>
Object.keys(objectOfFunctions).forEach((key: keyof MapObj) => {
result[key] = objectOfFunctions[key].toResponse(objectOfValues[key]);
})
return result
}
})
This gives us the errors that we want on invalid values. It doesn't give us error on excess properties, yet, but I'm just going to give up and hit "Post" because while I was typing this @jcalz came along and answered .
Edit: changed Object.keys(objectOfValues)
to Object.keys(objectOfFunctions)
as suggested by @jcalz. You still won't get errors on excess properties, but they'll be dropped from the result and won't cause errors.
Typescript Playground Link.