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:

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
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.