Issue
Basically, I have this type
type X = {
a: {
b?: string;
c?: string;
d: {
e:{
f:{
g?: boolean
}
}
};
};
};
// and a helper
const as = <T,>(arg: T):T => arg;
and I need to be able to create an object, in the shape of X where all properties, on all levels of nesting, are replaced by:
as({
// autocomplete here with actual object property
// unless it's not an object
})
// like so
const x = as<X>({
a: as({
b: as("some string"),
d: as({
e: as({
f: as({
g: as(false)
})
})
})
})
})
I got it working to a degree, but at more than 2 levels of nesting, the autocomplete no longer ...autocompletes
Please refer to the Typescript playground link for a better understanding of the problem.
Solution
So... with the help of somebody from typescript's Discord community, i got it working.
Final working example:
type IsObject<T> = T extends Array<any>
? false
: T extends Date
? false
: T extends object
? true
: false;
type BuiltInFn = "someFn";
type NullableUndefinedOrEmptyUnion = "nullable" | "undefined" | "emptyString";
type Primitive<T> = T extends string
? "string"
: T extends boolean
? "boolean"
: T extends number
? "number"
: never;
type NN<T> = NonNullable<T>;
type S<T, R = T> = IsObject<NN<T>> extends true
? {
[K in keyof T]?: IsObject<NN<T[K]>> extends false
? S<NN<T[K]>, R>
:
| (<XX extends S<NN<T[K]>, R>>(
value: XX | undefined,
root: R
) => boolean | void)
| (<XX extends S<NN<T[K]>, R>>(
value: <YY extends S<NN<T[K]>, R>>(value: YY, root: R) => boolean | void,
root: R
) => XX | void);
}
:
| (<XX extends NN<T>>(
value: Primitive<XX> extends never
? BuiltInFn | undefined
: Primitive<XX> | undefined,
root: R
) => XX | void)
| (<XX extends NN<T>>(
value: <YY extends NN<T>>(value: YY, root: R) => boolean | void,
root: R
) => XX | void);
function s<T>(s: S<T, T>): S<T, T> {
return s;
}
type A<T> = T extends (value: infer U, root: infer UU) => infer Z ? U : never;
function as<T>(arg: A<T>, ...opts: Array<BuiltInFn | NullableUndefinedOrEmptyUnion>): NN<T> {
return arg as any;
}
type X = {
a?: {
b?: string;
c?: number;
d: {
nested?: {
e: {
f: boolean;
};
g: {
x: number;
};
};
};
};
};
const x = s<X>({
a: as({
d: as({
nested: as({
e: as({
f: as("boolean"),
}),
}),
}),
b: as("string"),
}),
});
Now you can use a custom function that can receive 3 types as an argument:
- the object property itself (if prop === object) with optional params
- an anonymous function which takes 2 arguments (the object property itself, and the root object)
- if the param is not an object, a string literal which represents the type of the param, or an anonymous function as described above
And... everything is typed no matter how deep
Beautiful!
Answered By - man
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.