Working with TypeScript, I am dealing with a nested object structure of functions defined as:
type CallbackFn = (args: any) => any
type CallbackObj = {
[key: string]: CallbackFn | CallbackObj
}
const callbacks = {
foo: function(args: { x: num }): string {
return "test";
},
bar: {
bar1: function(): boolean {
return true;
},
bar2: function(): number {
return 10;
}
},
baz: {
baz1: {
baz2: function(args: { key: string }): string {
return "test";
}
}
}
}
In another section of the system, there is an interface definition structured like this:
interface FieldDef {
name: string
type: string
callback: CallbackDef
}
interface CallbackDef {
fn: string
settings: any
}
The main objective is to enable auto-completion for users when specifying which callback function to use for a specific FieldDef
, and then facilitate auto-completion for the settings associated with that callback. In the given cases, the potential entries for fn
are
"foo" | "bar.bar1" | "bar.bar2" | "baz.baz1.baz2"
and the settings
vary based on the specific fn
referenced in the definition. The names of the functions represent concatenated dot paths indicating the nesting of callbacks. To achieve this, I have been attempting to build a discriminated union. If I could generate the following union dynamically, it should work, theoretically.
type CallbackDef = {
name: "foo",
settings: {
x: num
}
} | {
name: "bar.bar1"
} | {
name: "bar.bar2"
} | {
name: "baz.baz1.baz2",
settings: {
key: string
}
}
I am struggling to generate this union dynamically based on the code-defined callbacks
object due to two primary issues. Firstly, a recursive type is essential to handle the multiple nesting levels efficiently. Secondly, the conventional method of using { [key in keyof T]: something }
has not yielded ideal results because each processed object needs to either return a single function possibility or, if it's an object, multiple functions. It seems like what is needed is a spread type of type definition, where each level returns a union of possibilities at that level. My current closest attempt is as follows:
type CallbackFn = (args: any) => any
type CallbackObj = {
[key: string]: CallbackFn | CallbackObj
}
const callbacks = {
foo: function(args: { x: number }): string {
return "test";
},
bar: {
bar1: function(): boolean {
return true;
},
bar2: function(): number {
return 10;
}
},
baz: {
baz1: {
baz2: function(args: { key: string }): string {
return "test";
}
}
}
}
type StringKeys<T> = Extract<keyof T, string>;
type Process<T> = {
[key in StringKeys<T>]: T[key] extends CallbackFn
? { [k in key]: T[key] }
: {
[k in StringKeys<T[key]> as `${key}.${k}`]: T[key][k]
}
}
type GetValues<T> = T[keyof T];
type A = Process<typeof callbacks>
type B = GetValues<A>
Perhaps there is a simpler approach to solve this issue. Any assistance or suggestions would be highly appreciated.