Similar to this question I'm trying to create an object of a mapped type. However, I keep getting type errors in the implementation.
Here's a toy example:
type SingleArgFunction<A> = (x: A) => A;
type ArrayedReturnFunction<A> = (x: A) => A[];
// create an arrayed function from a single arg function
type MakeArrayed<F> = F extends SingleArgFunction<infer A> ? ArrayedReturnFunction<A> : never;
// mapped type
type MakeArrayedAll<FS> = {
[K in keyof FS]: MakeArrayed<FS[K]>
}
// example usage
type MyFunctions = {
foo: SingleArgFunction<number>,
bar: SingleArgFunction<string>
}
type MyArrayedFunctions = MakeArrayedAll<MyFunctions> // ok
// functions to convert an object of single arg functions to object of arrayed functions
interface SingleArgFunctionObject {
[key: string]: SingleArgFunction<any>
}
function makeArrayed<A>(f: SingleArgFunction<A>): ArrayedReturnFunction<A> {
return function (x) {
return [f(x)];
}
}
function makeArrayedAll<FS extends SingleArgFunctionObject>(fs: FS): MakeArrayedAll<FS> {
const keys = Object.keys(fs) as (keyof FS)[];
const result = {} as MakeArrayedAll<FS>;
for (let key of keys) {
result[key] = makeArrayed(fs[key]);
}
return result;
}
This gives the following type error for result
:
const result: MakeArrayedAll<FS>
Type 'ArrayedReturnFunction<any>' is not assignable to type 'MakeArrayed<FS[keyof FS]>'.(2322)
The difference with the previous question is that the properties of the unmapped type FS
need to be constrained via FS extends SingleArgFunctionObject
. Is there a solution other than asserting result as any
?
I'd be inclined to refactor your types so as to avoid conditional types (which are hard for the compiler to reason about) and instead use mapped types and inference from mapped types as much as possible. Your makeArrayed()
function looks fine to me as-is.
I'll define SingleArgFunctionObject
and ArrayedReturnFunctionObject
to be generic in the object type AS
that it maps over... the idea is that each property of AS
is treated like the A
in your SingleArgFunction<A>
and ArrayedReturnFunction<A>
:
type SingleArgFunctionObject<AS extends object> = {
[K in keyof AS]: SingleArgFunction<AS[K]>
}
type ArrayedReturnFunctionObject<AS extends object> = {
[K in keyof AS]: ArrayedReturnFunction<AS[K]>
}
Armed with this, makeArrayedAll()
will be generic in AS
. The tricky bit for getting the compiler not to complain is to make the for
loop treat each key
as a generic K extends keyof AS
. That's easier to do with the forEach()
method of arrays:
function makeArrayedAll<AS extends object>(
fs: SingleArgFunctionObject<AS>
): ArrayedReturnFunctionObject<AS> {
const result = {} as ArrayedReturnFunctionObject<AS>;
(Object.keys(fs) as (keyof AS)[]).forEach(<K extends keyof AS>(key: K) => {
result[key] = makeArrayed(fs[key]);
})
return result;
}
That should produce the same result as your code, but now the compiler is more confident that makeArrayed(fs[key])
of type ArrayedReturnFunction<AS[K]>
is assignable to result[key]
of type ArrayedReturnFunctionObject<AS>[K]
.
Okay, hope that helps; good luck!