Search code examples
typescriptgenericstypesmapped-types

Omit readonly property when mapping type


I can't figure out a way to discard readonly props when mapping a type to another.

Scenario:

type SimplePOJO = {
    first_name: string;
    readonly last_name: string;
    year: number;
}

type Mapper<T extends SimplePOJO> = {
    [K in keyof T]: { name: K, type: T[K] }
}

// I also tried this without any luck:
//
// type Mapper<T extends SimplePOJO> = {
//    [K in keyof T]: T[K] extends Readonly<T[K]> ? never : { name: K, type: T[K] }
// }


type MappedPOJO = Mapper<SimplePOJO>

Desired outcome:

{
    first_name: {
        name: "first_name";
        type: string;
    };
    year: {
        name: "year";
        type: number;
    };
}

Actual result:

{
    first_name: {
        name: "first_name";
        type: string;
    };
    readonly last_name: {
        name: "last_name";
        type: string;
    };
    year: {
        name: "year";
        type: number;
    };
}

Is this possible with TypeScript?


Solution

  • There is a way to identify the writable keys and only keep them.

    Example in the following snippet

    type SimplePOJO = {
        first_name: string;
        readonly last_name: string;
        year: number;
    }
    
    type IfEquals<X, Y, A=X, B=never> =
      (<T>() => T extends X ? 1 : 2) extends
      (<T>() => T extends Y ? 1 : 2) ? A : B;
    
    type WritableKeys<T> = {
      [P in keyof T]-?: IfEquals<{ 
        [Q in P]: T[P];
      }, { 
        -readonly [Q in P]: T[P];
      }, P>
    }[keyof T];
    
    type Mapper<T extends SimplePOJO> = Pick<{
        [K in keyof T]: { 
          name: K;
          type: T[K];
        };
    }, WritableKeys<T>>;
    
    type MappedPOJO = Mapper<SimplePOJO>;
    

    Look at @jcalz explaination for the IfEquals and WritableKeys


    A simpler solution could be for you to specify the key to remove :

    snippet

    type SimplePOJO = {
        first_name: string;
        readonly last_name: string;
        year: number;
    }
    
    type Mapper<T extends SimplePOJO> = Omit<{
        [K in keyof T]: { 
          name: K;
          type: T[K];
        };
    }, 'last_name'>;
    
    type MappedPOJO = Mapper<SimplePOJO>;