Reputation: 521
This question is very similar to a previous one I made.
I recommend reading my earlier question first: How to copy the structure of one generic type to another generic in TypeScript?
Rather than cloning the structure of a flat object type, I'm looking to clone the structure of a nested object type.
In orther words, I'm looking for a function that, given...
// ...this input type
interface NestedInput {
name: string;
arr: [
string,
Date,
{a: boolean}
];
nestedObject: {
x: number;
y: number;
};
}
// ...it produces this output type
type StringMethod = (val: string) => void;
type DateMethod = (val: Date) => void;
type NumMethod = (val: number) => void;
type BoolMethod = (val: boolean) => void;
interface NestedOutput {
name: StringMethod;
arr: [
StringMethod,
DateMethod,
{
a: BoolMethod;
}
];
nestedObject: {
x: NumberMethod;
y: NumberMethod;
}
}
Once again, it must be completely type-safe, such that I can access output.nestedObject.x
or output.arr[2].a
using intellisense.
I've been racking my brain the last 2 days trying to figure this out, so any help would be greatly appreciated!
PS: You may have noticed that we run into the problem of defining when we traverse a nested object. A Date
object for instance wouldn't be traversed, but some other structure might. To prevent this from being a problem, you can assume that if the object is a vanilla JS object (see function below), then it's ok to traverse.
const getClass: (object: any) => string = Function.prototype.call.bind(Object.prototype.toString);
const isVanillaObject = (obj: any) => {
return getClass(obj) === "[object Object]";
}
Upvotes: 2
Views: 740
Reputation: 7346
You can use extends
to switch on types, a mapped type for the object case and use recursion to allow deep nesting:
interface NestedInput {
name: string;
arr: [string, Date, { a: boolean }];
nestedObject: {
x: number;
y: number;
};
}
type StringMethod = (val: string) => void;
type DateMethod = (val: Date) => void;
type NumMethod = (val: number) => void;
type BoolMethod = (val: boolean) => void;
type Methodify<T> = T extends string
? StringMethod
: T extends Date
? DateMethod
: T extends number
? NumMethod
: T extends boolean
? BoolMethod
: {
[K in keyof T]: Methodify<T[K]>;
};
type Output = Methodify<NestedInput>;
//Results in:
type Output = {
name: StringMethod;
arr: [StringMethod, DateMethod, {
a: BoolMethod;
}];
nestedObject: {
x: NumMethod;
y: NumMethod;
};
}
Upvotes: 2