Say I have a type like this:
type MyType = {
a: string | null | undefined;
b: boolean | undefined;
c: number;
}
I want to remove the null from property a, and removed undefined from b, so I get this:
type NewType = {
a: string | undefined;
b: boolean;
c: number;
}
I tried coming up with a utility type to do this, but for some reason its not working:
type CleanType<T extends object> = {
[P in keyof T]: T[P] extends infer TP
? TP extends null
? NonNull<TP>
: NonUndefined<TP>
: never
type NonUndefined<T> = T extends undefined ? never : T;
type NonNull<T> = T extends null ? never : T
However, this gives these results:
type MyType = CleanType<MyType>
MyType: {
a: string;
b: boolean;
c: number;
}
It took away the undefined even from the a property unfortunately. Does anyone know what I'm doing wrong? Thanks.
CodePudding user response:
Your problem is that by copying T[P] to its own generic type parameter TP (via conditional type inference), your subsequent check of TP extends null ? NonNull<TP> : NonUndefined<TP> is a distributive conditional type, and so that check will happen separately for each union member of TP. That means string | null | undefined will be checked for string extends null (nope), null extends null (yep), and undefined extends null (nope), and thus you get NonUndefined<string> | NonNull<null> | NonUndefined<undefined>, which is string | never | never, which is string. Oops.
You really don't want to split T[P] into union pieces before checking it. Instead, you should check it all at once:
type CleanType<T extends object> = {
[P in keyof T]: null extends T[P] ? NonNull<T[P]> : NonUndefined<T[P]>
};
We don't want to check T[P] extends null since that will not work (string | null | undefined certainly doesn't extend null) but the reverse does null extends string | null | undefined.
Let's test it out:
type MyCleanType = CleanType<MyType>
/* type MyCleanType = {
a: string | undefined;
b: boolean;
c: number;
} */
Looks good.
CodePudding user response:
Try:
type CleanType<T extends object> = {
[P in keyof T]: T[P] extends infer TP
? TP extends null & undefined // intersect
? NonNull<TP>
: NonUndefined<TP>
: never
};
This removes null from property a and removes undefined from property b. The idea is to target the property with an intersection of both types.
