I have an undetermined number of mods in an input object:
const mod1 = {
actions: {
a() { },
b() { },
}
}
const mod2 = {
actions: {
c() { },
d() { },
}
}
const input = {
mods: {
mod1,
mod2
}
}
At run time, a lib merges the mods in a single object which is equivalent to:
const output = {
actions: {
a() { },
b() { },
c() { },
d() { },
}
}
And I would like to create a type that would describe this single object.
The input objects can be described like that:
interface Input {
mods: Mods
}
interface Mods {
[name: string]: Mod
}
interface Mod {
actions: {
[name: string]: () => void
}
}
Then, I don't know how to merge the content of mods:
interface ToOutput<I extends Input> {
actions: MergeMods<I["mods"]>
}
type MergeMods<M extends Mods> = // How to merge the content of 'M'?
Here is a solution:
type Output = ToOutput<(typeof input)["mods"]>
interface ToOutput<I extends Mods> {
actions: UnionToIntersection<I[keyof I]["actions"]>
}
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
The following type:
interface ToOutput<I extends Input> {
actions: UnionToIntersection<I["mods"][keyof I["mods"]]["actions"]>
}
first uses keyof
and lookups to map the Input
type to match the output structure
interface ToOutputStep1<I extends Input> {
actions: I["mods"][keyof I["mods"]]["actions"]
}
/*
type T1 = {
actions:
| { a: {}; b: {}; }
| { c: {}; d: {}; };
}
*/
type T1 = ToOutputStep1<typeof input>
and then converts the actions
union type to an intersection type.
/*
type T2 = {
actions: {
a: {};
b: {};
c: {};
d: {};
};
}
*/
type T2 = ToOutput<typeof input>