Tipos mapped

Avanzado
TypeScript
TypeScript
Actualizado: 17/04/2026

Sintaxis de mapped types

Los mapped types crean nuevos tipos iterando sobre las claves de un tipo existente. La sintaxis base utiliza la construcción [K in keyof T] para recorrer cada propiedad y transformarla:

Anatomia de mapped types

type MiReadonly<T> = {
    readonly [K in keyof T]: T[K]
}

interface Usuario {
    nombre: string
    email: string
    edad: number
}

type UsuarioReadonly = MiReadonly<Usuario>
// { readonly nombre: string; readonly email: string; readonly edad: number }

Cada parte de la sintaxis cumple una función:

  • K in keyof T: itera sobre cada clave de T, asignando la clave actual a K
  • T[K]: accede al tipo de la propiedad K en T (tipo de acceso indexado)
  • El resultado es un nuevo tipo con la misma estructura pero con las transformaciones aplicadas
type Opcionalizar<T> = {
    [K in keyof T]?: T[K]
}

type UsuarioOpcional = Opcionalizar<Usuario>
// { nombre?: string; email?: string; edad?: number }

Los mapped types son la base de muchos utility types integrados de TypeScript como Partial, Required, Readonly y Record. Comprender su sintaxis permite crear utilidades personalizadas.

Iterar sobre uniones de strings

Los mapped types no se limitan a keyof. Pueden iterar sobre cualquier unión de strings:

type Banderas<T extends string> = {
    [K in T]: boolean
}

type Permisos = Banderas<"leer" | "escribir" | "eliminar">
// { leer: boolean; escribir: boolean; eliminar: boolean }

const permisos: Permisos = {
    leer: true,
    escribir: true,
    eliminar: false
}
type Contadores<T extends string> = {
    [K in T]: number
}

type EstadisticasPagina = Contadores<"visitas" | "clics" | "conversiones">

const stats: EstadisticasPagina = {
    visitas: 1500,
    clics: 320,
    conversiones: 45
}

Modificadores: readonly y opcionalidad

Los mapped types admiten modificadores que controlan si una propiedad es de solo lectura o es opcional. Los prefijos + y - permiten añadir o eliminar estos modificadores.

Añadir modificadores

// Hacer todas las propiedades readonly y opcionales
type ReadonlyOpcional<T> = {
    +readonly [K in keyof T]+?: T[K]
}

interface Config {
    host: string
    puerto: number
    debug: boolean
}

type ConfigInmutable = ReadonlyOpcional<Config>
// { readonly host?: string; readonly puerto?: number; readonly debug?: boolean }

El prefijo + es el comportamiento por defecto, así que +readonly equivale a escribir solo readonly, y +? equivale a ?.

Eliminar modificadores

El prefijo - elimina un modificador existente:

// Eliminar readonly de todas las propiedades
type Mutable<T> = {
    -readonly [K in keyof T]: T[K]
}

type UsuarioMutable = Mutable<Readonly<Usuario>>
// { nombre: string; email: string; edad: number }
// Eliminar opcionalidad (equivalente a Required)
type Obligatorio<T> = {
    [K in keyof T]-?: T[K]
}

interface Formulario {
    nombre?: string
    email?: string
    telefono?: string
}

type FormularioCompleto = Obligatorio<Formulario>
// { nombre: string; email: string; telefono: string }

Combinar eliminación de modificadores

// Eliminar tanto readonly como opcionalidad
type MutableRequerido<T> = {
    -readonly [K in keyof T]-?: T[K]
}

interface DatosParciales {
    readonly id?: number
    readonly nombre?: string
    readonly activo?: boolean
}

type DatosCompletos = MutableRequerido<DatosParciales>
// { id: number; nombre: string; activo: boolean }

El modificador -readonly solo tiene efecto si la propiedad original era readonly. De la misma forma, -? solo tiene efecto sobre propiedades opcionales. Aplicarlos sobre propiedades que no tienen esos modificadores no produce error.

Key remapping con as

La cláusula as dentro de un mapped type permite renombrar las claves durante la iteración. Esta funcionalidad se combina con template literal types para crear transformaciones de nombres de propiedad:

type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

interface Persona {
    nombre: string
    edad: number
    activo: boolean
}

type PersonaGetters = Getters<Persona>
// { getNombre: () => string; getEdad: () => number; getActivo: () => boolean }

Setters con key remapping

type Setters<T> = {
    [K in keyof T as `set${Capitalize<string & K>}`]: (valor: T[K]) => void
}

type PersonaSetters = Setters<Persona>
// { setNombre: (valor: string) => void; setEdad: (valor: number) => void; setActivo: (valor: boolean) => void }

Manejadores de eventos

type Manejadores<T> = {
    [K in keyof T as `on${Capitalize<string & K>}Change`]: (
        anterior: T[K],
        nuevo: T[K]
    ) => void
}

type PersonaManejadores = Manejadores<Persona>
// {
//   onNombreChange: (anterior: string, nuevo: string) => void
//   onEdadChange: (anterior: number, nuevo: number) => void
//   onActivoChange: (anterior: boolean, nuevo: boolean) => void
// }

Prefijos y sufijos arbitrarios

type ConPrefijo<T, Prefijo extends string> = {
    [K in keyof T as `${Prefijo}_${string & K}`]: T[K]
}

interface Dimensiones {
    ancho: number
    alto: number
}

type DimensionesMin = ConPrefijo<Dimensiones, "min">
// { min_ancho: number; min_alto: number }

type DimensionesMax = ConPrefijo<Dimensiones, "max">
// { max_ancho: number; max_alto: number }

Filtrado de claves

La cláusula as combinada con tipos condicionales permite filtrar propiedades produciendo never para las claves que se quieren excluir:

type SoloPropiedadesDeTipo<T, TipoDeseado> = {
    [K in keyof T as T[K] extends TipoDeseado ? K : never]: T[K]
}

interface Empleado {
    id: number
    nombre: string
    email: string
    salario: number
    activo: boolean
    departamento: string
}

type PropiedadesString = SoloPropiedadesDeTipo<Empleado, string>
// { nombre: string; email: string; departamento: string }

type PropiedadesNumber = SoloPropiedadesDeTipo<Empleado, number>
// { id: number; salario: number }

Cuando un mapped type produce never como nombre de clave, esa propiedad se elimina del tipo resultante. Este mecanismo es la base del filtrado de propiedades.

Excluir propiedades por tipo

type ExcluirPropiedadesDeTipo<T, TipoExcluido> = {
    [K in keyof T as T[K] extends TipoExcluido ? never : K]: T[K]
}

type EmpleadoSinBooleanos = ExcluirPropiedadesDeTipo<Empleado, boolean>
// { id: number; nombre: string; email: string; salario: number; departamento: string }

Filtrar por nombre de propiedad

type ExcluirPorPrefijo<T, Prefijo extends string> = {
    [K in keyof T as K extends `${Prefijo}${string}` ? never : K]: T[K]
}

interface DatosCompletos {
    nombre: string
    email: string
    _interno: number
    _cache: boolean
    visible: boolean
}

type DatosPublicos = ExcluirPorPrefijo<DatosCompletos, "_">
// { nombre: string; email: string; visible: boolean }

Utility types personalizados con mapped types

Hacer opcionales solo ciertas propiedades

type HacerOpcional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

interface Producto {
    id: number
    nombre: string
    descripcion: string
    precio: number
    stock: number
}

type CrearProducto = HacerOpcional<Producto, "id" | "stock">
// id y stock son opcionales, el resto obligatorio

const producto: CrearProducto = {
    nombre: "Monitor",
    descripcion: "Monitor 27 pulgadas",
    precio: 300
}

Hacer readonly solo ciertas propiedades

type ReadonlyParcial<T, K extends keyof T> = Readonly<Pick<T, K>> & Omit<T, K>

type ProductoConIdFijo = ReadonlyParcial<Producto, "id">

const p: ProductoConIdFijo = { id: 1, nombre: "Teclado", descripcion: "Mecanico", precio: 75, stock: 10 }
p.nombre = "Teclado Pro"  // OK
// p.id = 2               // Error: readonly

Tipo que convierte valores a Promises

type Asyncify<T> = {
    [K in keyof T]: T[K] extends (...args: infer A) => infer R
        ? (...args: A) => Promise<R>
        : T[K]
}

interface ServicioSync {
    obtener(id: number): string
    listar(): string[]
    contador: number
}

type ServicioAsync = Asyncify<ServicioSync>
// {
//   obtener: (id: number) => Promise<string>
//   listar: () => Promise<string[]>
//   contador: number
// }

Tipo que extrae solo métodos

type SoloMetodos<T> = {
    [K in keyof T as T[K] extends Function ? K : never]: T[K]
}

type SoloPropiedades<T> = {
    [K in keyof T as T[K] extends Function ? never : K]: T[K]
}

class Servicio {
    nombre = "api"
    versión = 1
    iniciar(): void {}
    detener(): void {}
    estado(): string { return "activo" }
}

type MetodosServicio = SoloMetodos<Servicio>
// { iniciar: () => void; detener: () => void; estado: () => string }

type PropiedadesServicio = SoloPropiedades<Servicio>
// { nombre: string; versión: number }

Tipos de acceso indexado

Los tipos de acceso indexado (T[K]) permiten obtener el tipo de una propiedad específica de un tipo. Son fundamentales dentro de los mapped types, pero también se usan de forma independiente:

interface Respuesta {
    datos: { id: number; nombre: string }[]
    estado: number
    mensaje: string
}

type TipoDatos = Respuesta["datos"]
// { id: number; nombre: string }[]

type ElementoDatos = Respuesta["datos"][number]
// { id: number; nombre: string }

type TipoEstado = Respuesta["estado"]
// number

Se pueden acceder múltiples propiedades usando una unión de claves:

type CamposTexto = Respuesta["mensaje" | "estado"]
// string | number

Acceso indexado con keyof

type TodosLosTipos<T> = T[keyof T]

interface Config {
    host: string
    puerto: number
    debug: boolean
}

type ValoresConfig = TodosLosTipos<Config>
// string | number | boolean

Acceso indexado en arrays y tuplas

const colores = ["rojo", "verde", "azul"] as const

type Color = typeof colores[number]
// "rojo" | "verde" | "azul"

type Tupla = [string, number, boolean]

type PrimerElemento = Tupla[0]    // string
type SegundoElemento = Tupla[1]   // number
type TercerElemento = Tupla[2]    // boolean

El operador keyof

El operador keyof produce una unión de las claves (nombres de propiedad) de un tipo. Es el complemento natural de los tipos de acceso indexado y la base de los mapped types:

interface Vehiculo {
    marca: string
    modelo: string
    ano: number
    electrico: boolean
}

type ClavesVehiculo = keyof Vehiculo
// "marca" | "modelo" | "ano" | "electrico"

keyof se usa frecuentemente en restricciones genéricas para garantizar acceso seguro a propiedades:

function obtenerValor<T, K extends keyof T>(obj: T, clave: K): T[K] {
    return obj[clave]
}

const coche: Vehiculo = {
    marca: "Toyota",
    modelo: "Corolla",
    ano: 2023,
    electrico: false
}

const marca = obtenerValor(coche, "marca")        // string
const electrico = obtenerValor(coche, "electrico") // boolean
// obtenerValor(coche, "color")                    // Error

keyof con tipos indexados

type Diccionario = { [clave: string]: number }

type ClavesDiccionario = keyof Diccionario
// string | number (number porque obj[0] es equivalente a obj["0"])

keyof con mapped types para transformaciones completas

type Validadores<T> = {
    [K in keyof T]: (valor: T[K]) => boolean
}

interface Pedido {
    producto: string
    cantidad: number
    precio: number
}

const validarPedido: Validadores<Pedido> = {
    producto: (v) => v.length > 0,
    cantidad: (v) => v > 0 && Number.isInteger(v),
    precio: (v) => v > 0
}

function validar<T>(datos: T, validadores: Validadores<T>): boolean {
    for (const clave in validadores) {
        if (!validadores[clave](datos[clave])) {
            return false
        }
    }
    return true
}

const pedido: Pedido = { producto: "Laptop", cantidad: 2, precio: 999 }
console.log(validar(pedido, validarPedido))  // true
type DiferenciasParciales<T> = {
    [K in keyof T]?: {
        anterior: T[K]
        nuevo: T[K]
    }
}

interface Estado {
    página: number
    filtro: string
    orden: "asc" | "desc"
}

const cambios: DiferenciasParciales<Estado> = {
    página: { anterior: 1, nuevo: 2 },
    orden: { anterior: "asc", nuevo: "desc" }
}

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en TypeScript

Documentación oficial de TypeScript
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de TypeScript

Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Comprender la sintaxis de mapped types con [K in keyof T]. Aplicar modificadores +/- readonly y +/- ?. Usar key remapping con as clause y filtrado de claves. Construir utility types personalizados con mapped types. Dominar tipos de acceso indexado T[K] y el operador keyof.