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.