Tipos de objeto y tipado estructural

Intermedio
TypeScript
TypeScript
Actualizado: 18/04/2026

Tipos de objeto inline

En TypeScript, los tipos de objeto describen la forma de un valor especificando las propiedades y sus tipos. La forma más directa de definir un tipo de objeto es mediante la sintaxis inline, directamente en la anotación de tipo:

Tipado estructural en TypeScript

function crearEtiqueta(config: { texto: string; color: string; tamano: number }): string {
  return `[${config.color}] ${config.texto} (${config.tamano}px)`
}

const etiqueta = crearEtiqueta({ texto: "Hola", color: "rojo", tamano: 14 })
console.log(etiqueta) // "[rojo] Hola (14px)"

Los tipos de objeto inline son útiles para parámetros y variables de uso puntual. Para tipos reutilizados en múltiples lugares, las interfaces o alias de tipo son preferibles:

// Variable con tipo de objeto inline
let coordenada: { x: number; y: number } = { x: 10, y: 20 }

// Retorno con tipo de objeto inline
function crearPunto(x: number, y: number): { x: number; y: number } {
  return { x, y }
}

const punto = crearPunto(5, 15)
console.log(punto.x) // 5
console.log(punto.y) // 15

Propiedades opcionales en tipos de objeto

Al igual que en las interfaces, las propiedades opcionales se marcan con ?:

function dibujar(figura: { tipo: string; ancho: number; alto?: number }): string {
  if (figura.alto !== undefined) {
    return `${figura.tipo}: ${figura.ancho}x${figura.alto}`
  }
  return `${figura.tipo}: ${figura.ancho}x${figura.ancho}`
}

console.log(dibujar({ tipo: "rectangulo", ancho: 100, alto: 50 }))
// "rectangulo: 100x50"

console.log(dibujar({ tipo: "cuadrado", ancho: 100 }))
// "cuadrado: 100x100"

Propiedades readonly en tipos de objeto

function procesar(datos: { readonly id: string; nombre: string }): void {
  console.log(`Procesando ${datos.nombre} (${datos.id})`)
  datos.nombre = datos.nombre.toUpperCase() // Permitido
  // datos.id = "nuevo-id" // Error: Cannot assign to 'id' because it is a read-only property
}

procesar({ id: "abc-123", nombre: "ejemplo" })

Tipado estructural

TypeScript utiliza un sistema de tipos estructural (también conocido como "duck typing"). Esto significa que la compatibilidad de tipos se determina por la forma del valor, no por su nombre o declaración explícita. Un objeto es compatible con un tipo si tiene al menos las propiedades requeridas con los tipos correctos.

interface Volador {
  volar(): string
}

interface Nadador {
  nadar(): string
}

// Este objeto satisface ambas interfaces por su estructura
const pato = {
  volar() { return "Volando" },
  nadar() { return "Nadando" },
  caminar() { return "Caminando" }
}

function hacerVolar(animal: Volador): string {
  return animal.volar()
}

function hacerNadar(animal: Nadador): string {
  return animal.nadar()
}

// pato es compatible con Volador porque tiene el método volar()
console.log(hacerVolar(pato)) // "Volando"

// pato es compatible con Nadador porque tiene el método nadar()
console.log(hacerNadar(pato)) // "Nadando"

En un sistema de tipos nominal (como Java o C#), el objeto tendría que implementar explícitamente cada interfaz. En TypeScript, basta con que la forma coincida.

Compatibilidad con propiedades adicionales

El tipado estructural permite que un objeto tenga más propiedades de las requeridas. Las propiedades adicionales no afectan la compatibilidad:

interface Identificable {
  id: number
}

interface ConNombre {
  nombre: string
}

const usuario = {
  id: 1,
  nombre: "Elena",
  email: "elena@mail.com",
  rol: "admin"
}

// usuario es compatible con Identificable (tiene id: number)
const ref1: Identificable = usuario
console.log(ref1.id) // 1

// usuario es compatible con ConNombre (tiene nombre: string)
const ref2: ConNombre = usuario
console.log(ref2.nombre) // "Elena"

Compatibilidad entre interfaces y tipos

Interfaces y alias de tipo con la misma forma son intercambiables:

interface PuntoInterface {
  x: number
  y: number
}

type PuntoType = {
  x: number
  y: number
}

const p1: PuntoInterface = { x: 10, y: 20 }
const p2: PuntoType = p1 // Compatible: misma forma

function distanciaAlOrigen(punto: PuntoInterface): number {
  return Math.sqrt(punto.x ** 2 + punto.y ** 2)
}

// Se puede pasar un PuntoType donde se espera PuntoInterface
const p3: PuntoType = { x: 3, y: 4 }
console.log(distanciaAlOrigen(p3)) // 5

Excess property checking

Aunque el tipado estructural permite propiedades adicionales en general, TypeScript aplica una verificación más estricta cuando se asigna un literal de objeto directamente a un tipo. Esta verificación se llama excess property checking y genera un error si el literal contiene propiedades que no existen en el tipo destino.

interface Opciones {
  ancho: number
  alto: number
  color?: string
}

// Error: Object literal may only specify known properties,
// and 'borde' does not exist in type 'Opciones'
// const opts: Opciones = { ancho: 100, alto: 200, borde: 5 }

// Correcto: solo propiedades conocidas
const optsValidas: Opciones = { ancho: 100, alto: 200, color: "azul" }
console.log(optsValidas.ancho) // 100

El excess property checking solo se aplica a literales de objeto asignados directamente. Si el objeto se asigna primero a una variable intermedia, la verificación no se aplica.

Evitar excess property checking con variables intermedias

Cuando el objeto se crea en una variable separada y luego se asigna, TypeScript solo aplica la compatibilidad estructural normal:

interface Tema {
  primario: string
  secundario: string
}

// Con literal directo: excess property checking activo
// const tema: Tema = { primario: "azul", secundario: "gris", acento: "verde" } // Error

// Con variable intermedia: solo compatibilidad estructural
const datosDelTema = { primario: "azul", secundario: "gris", acento: "verde" }
const tema: Tema = datosDelTema // Sin error
console.log(tema.primario) // "azul"

Excess property checking en parámetros de función

La misma regla se aplica cuando se pasan literales de objeto como argumentos:

interface FiltroConsulta {
  campo: string
  valor: string
  exacto?: boolean
}

function ejecutarConsulta(filtro: FiltroConsulta): string {
  const modo = filtro.exacto ? "exacto" : "parcial"
  return `Buscando ${filtro.campo}=${filtro.valor} (${modo})`
}

// Error: 'limite' no existe en FiltroConsulta
// ejecutarConsulta({ campo: "nombre", valor: "Ana", limite: 10 })

// Correcto
console.log(ejecutarConsulta({ campo: "nombre", valor: "Ana", exacto: true }))
// "Buscando nombre=Ana (exacto)"

// Con variable intermedia: sin excess property checking
const filtro = { campo: "email", valor: "@mail.com", limite: 10 }
console.log(ejecutarConsulta(filtro))
// "Buscando email=@mail.com (parcial)"

Index signatures

Las index signatures describen objetos cuyas claves no se conocen de antemano pero cuyos valores tienen un tipo específico. La sintaxis utiliza corchetes con el tipo de la clave y el tipo del valor:

interface Diccionario {
  [clave: string]: number
}

const puntuaciones: Diccionario = {}
puntuaciones["matematicas"] = 95
puntuaciones["historia"] = 82
puntuaciones["ciencias"] = 91

console.log(puntuaciones["matematicas"]) // 95

Tipos de clave permitidos

Las index signatures admiten claves de tipo string, number, symbol y combinaciones de estos:

interface PorIndice {
  [indice: number]: string
}

const colores: PorIndice = {
  0: "rojo",
  1: "verde",
  2: "azul"
}

console.log(colores[0]) // "rojo"
console.log(colores[2]) // "azul"

Combinar index signatures con propiedades fijas

Las propiedades fijas pueden coexistir con index signatures, pero sus tipos deben ser compatibles con el tipo de la index signature:

interface Entorno {
  NODE_ENV: string
  PORT: string
  [clave: string]: string
}

const env: Entorno = {
  NODE_ENV: "production",
  PORT: "3000",
  DATABASE_URL: "postgres://localhost/db",
  API_KEY: "abc123"
}

console.log(env.NODE_ENV)       // "production"
console.log(env["DATABASE_URL"]) // "postgres://localhost/db"

Cuando las propiedades fijas tienen tipos diferentes, la index signature debe usar una union que los incluya:

interface Configuración {
  nombre: string
  versión: number
  [clave: string]: string | number | boolean
}

const config: Configuración = {
  nombre: "MiApp",
  versión: 2,
  debug: true,
  entorno: "desarrollo"
}

console.log(config.nombre)    // "MiApp"
console.log(config["debug"])  // true

Index signatures readonly

Una index signature puede marcarse como readonly para impedir la modificación de valores:

interface TablaConstantes {
  readonly [clave: string]: number
}

const constantes: TablaConstantes = {
  PI: 3.14159,
  E: 2.71828,
  PHI: 1.61803
}

console.log(constantes["PI"]) // 3.14159
// constantes["PI"] = 3.15   // Error: Index signature in type 'TablaConstantes' only permits reading

Tipos de objeto anidados y Readonly

Tipos de objeto anidados

Los tipos de objeto pueden contener propiedades cuyo tipo es otro objeto, creando estructuras anidadas:

interface Direccion {
  calle: string
  ciudad: string
  codigoPostal: string
  pais: string
}

interface Empresa {
  nombre: string
  sector: string
  dirección: Direccion
  contacto: {
    telefono: string
    email: string
    web?: string
  }
}

const empresa: Empresa = {
  nombre: "TechCorp",
  sector: "Tecnologia",
  dirección: {
    calle: "Calle Principal 42",
    ciudad: "Madrid",
    codigoPostal: "28001",
    pais: "Espania"
  },
  contacto: {
    telefono: "+34 600 000 000",
    email: "info@techcorp.com",
    web: "https://techcorp.com"
  }
}

console.log(empresa.dirección.ciudad)     // "Madrid"
console.log(empresa.contacto.email)       // "info@techcorp.com"
console.log(empresa.contacto.web)         // "https://techcorp.com"

Utility type Readonly

El tipo de utilidad Readonly<T> convierte todas las propiedades de un tipo en readonly, creando una versión inmutable superficial:

interface Estado {
  usuario: string
  autenticado: boolean
  intentos: number
}

const estadoInicial: Readonly<Estado> = {
  usuario: "admin",
  autenticado: false,
  intentos: 0
}

// Error: Cannot assign to 'autenticado' because it is a read-only property
// estadoInicial.autenticado = true

// Error: Cannot assign to 'intentos' because it is a read-only property
// estadoInicial.intentos = 1

console.log(estadoInicial.usuario) // "admin"

Readonly es superficial: solo protege las propiedades del primer nivel. Las propiedades anidadas siguen siendo mutables:

interface Aplicación {
  nombre: string
  config: {
    tema: string
    idioma: string
  }
}

const app: Readonly<Aplicación> = {
  nombre: "MiApp",
  config: {
    tema: "oscuro",
    idioma: "es"
  }
}

// Error: propiedad del primer nivel
// app.nombre = "OtraApp"

// Permitido: la propiedad anidada no es readonly
app.config.tema = "claro"
console.log(app.config.tema) // "claro"

Readonly con funciones

Readonly es útil en parámetros de función para garantizar que la función no modifique el objeto recibido:

interface Pedido {
  id: number
  productos: string[]
  total: number
}

function calcularDescuento(pedido: Readonly<Pedido>, porcentaje: number): number {
  // pedido.total = 0 // Error: readonly
  return pedido.total * (porcentaje / 100)
}

const pedido: Pedido = {
  id: 1,
  productos: ["Laptop", "Raton"],
  total: 1024
}

const descuento = calcularDescuento(pedido, 10)
console.log(descuento) // 102.4
console.log(pedido.total) // 1024 (sin modificar)

Combinación de readonly con index signatures

type CacheInmutable = Readonly<{
  [clave: string]: string
}>

const cache: CacheInmutable = {
  "/api/usuarios": '{"datos": []}',
  "/api/productos": '{"datos": []}'
}

console.log(cache["/api/usuarios"]) // '{"datos": []}'
// cache["/api/nuevaruta"] = "datos" // Error: readonly
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

Definir tipos de objeto inline con anotaciones de tipo literal. Comprender el tipado estructural y la compatibilidad por forma. Entender el excess property checking y como funciona. Usar index signatures para objetos con claves dinamicas. Aplicar Readonly para inmutabilidad superficial.