Home > Software design >  Typescipt remove null from Nullish properties and undefined from other properties?
Typescipt remove null from Nullish properties and undefined from other properties?

Time:04-29

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.

Playground link to code

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.

  • Related