Búsqueda de sitios web

typescript - Hacer opcionales las claves que tengan un carácter específico en su nombre


Di que tengo un objeto como:

const obj = {
  a: 1,
  "b?": 2,
  "c?": 3
}

Su tipo actualmente es:

type Obj = {
  a: number;
  "b?": number;
  "c?": number;
}

¿Hay alguna manera de crear un tipo que acepte obj y convierta su tipo a:

type Obj = {
  a: number;
  b?: number;
  c?: number;
}

Esencialmente haciendo que b y c sean opcionales.

Respuesta #1

Puedes hacerlo utilizando tipos de literales de plantilla e inferencia. Dividiéndolo en partes (pero las combinaremos al final):

  1. Primero queremos obtener las propiedades requeridas:

    type MapRequiredKeys<T> = {
        [Key in keyof T as Key extends `${string}?` ? never : Key]: T[Key];
    };
    

    Ahí, si Key coincide con el patrón $ {string}?, lo omitimos usando never como la clave; de lo contrario, usamos la clave. Usando eso en typeof obj nos da { a: number; }.

  2. Luego queremos los opcionales, y solo queremos la parte base de la clave, no el ?. Ahí es donde entra infer:

    type MapOptionalKeys<T> = {
        [Key in keyof T as Key extends `${infer BaseKey}?` ? BaseKey : never]?: T[Key];
    };
    

    Comparamos Key con $ {infer BaseKey}? y luego usamos BaseKey si coincide (dejando de lado la propiedad por completo si no coincide). Usamos ? en ella para hacer que la propiedad sea opcional.

  3. Combinándolos:

    type MapKeys<T> = MapRequiredKeys<T> & MapOptionalKeys<T>;
    
    type ObjType = MapKeys<typeof obj>;
    

    Ejemplo de patio de juegos

Pero podemos combinarlos en un solo tipo, lo que hace que las sugerencias que TypeScript nos da sean más claras:

type MapKeys<T> = {
    [Key in keyof T as Key extends `${string}?` ? never : Key]: T[Key];
} & {
    [Key in keyof T as Key extends `${infer BaseKey}?` ? BaseKey : never]?: T[Key];
};

type ObjType = MapKeys<typeof obj>;

Ejemplo de patio de juegos

Si pasas el cursor sobre ObjType allí, ves type ObjType={ a: number; } & { b?: number | undefined, c?: number | undefined; } en lugar de ver las "llamadas" a MapRequiredKeys y MapOptionalKeys.