Search code examples
typescriptnullundefinedconditional-typesmapped-types

Convert optional properties of a TypeScript interface to nullable properties


I have the following type:

interface A {
  p1: string
  p2?: string
}

I would like to generate a sub-type B with optional properties converted to nullable properties. Something equivalent to:

interface B {
  p1: string
  p2: string | null
}

I tried something like this:

type VuexPick<T, K extends keyof T> = {
  [P in K]-?: T[P] extends undefined ? null : T[P];
};

type B = VuexPick<A, "p1" | "p2">;

But it doesn't work. Any idea?


Solution

  • Your type B gets resolved to this:

    type B = {
      p1: string extends undefined ? null : string
      p2: string | undefined extends undefined ? null : string | undefined
    };
    

    string | undefined as supertype does not extend undefined, it is the other way around. So you end up with this type:

    type B = {
        p1: string;
        p2: string;
    }
    

    You could instead create a UndefinedToNull type, which causes a distributive conditional type to be applied, as T is a naked type parameter now.

    type VuexPick2<T, K extends keyof T> = {
      [P in K]-?: UndefinedToNull<T[P]>;
    };
    
    type UndefinedToNull<T> = T extends undefined ? null : T
    
    type B2 = VuexPick2<A, "p1" | "p2">; // type B2 = { p1: string; p2: string | null;}
    

    E.g. type UndefinedToNull<string | undefined> is the same as B4:

    type B4 = 
    | (string extends undefined ? null: string) 
    | (undefined extends undefined ? null: undefined) // type B4 = string | null
    

    Playground