If you know Mapping
structure upfront, you can do it.
First of allm you need to use mapped types to create all possible/allowed data structures:
interface Mapping {
"x": (a: string) => void
"y": (b: number) => void
}
type Variants<Dictionary> = {
[Prop in keyof Dictionary]: {
name: Prop,
fn: Dictionary[Prop]
}
}
// type Result = {
// x: {
// name: "x";
// fn: (a: string) => void;
// };
// y: {
// name: "y";
// fn: (b: number) => void;
// };
// }
type Result = Variants<Mapping>
As you might have noticed, we are ended up with nested object where values represents allowed state. Now, we need to somehow obtain union of allowed values or in other words make a discriminated union type
Consider this:
type Values<T> = T[keyof T]
interface Mapping {
"x": (a: string) => void
"y": (b: number) => void
}
type Variants<Dictionary> = {
[Prop in keyof Dictionary]: {
name: Prop,
fn: Dictionary[Prop]
}
}
// type Result = {
// name: "x";
// fn: (a: string) => void;
// } | {
// name: "y";
// fn: (b: number) => void;
// }
type Result = Values<Variants<Mapping>>
I have wrapped our result in Values
utility type. This type returns a union of all object values without keys. This is actually what we want.
We also can refactor it a bit:
type Values<T> = T[keyof T]
interface Mapping {
"x": (a: string) => void
"y": (b: number) => void
}
type Variants<Dictionary> = Values<{
[Prop in keyof Dictionary]: {
name: Prop,
fn: Dictionary[Prop]
}
}>
type Handlers = Variants<Mapping>
const x: Handlers = {
name: "x",
fn(prop /* :string */) { }
}
const y: Handlers = {
name: "y",
fn(prop /* :number */) { }
}
Playground
You don't need to use any extra generic arguments.
Here you can find similar question and here you can find my article.