Menu

Map en TypeScript: Map<K, V>, get, set, has y recorrerlo

Cómo usar Map en TypeScript: crear un Map<K, V> tipado, por qué get devuelve V | undefined, set, has y delete, recorrerlo en orden de inserción, claves que son objetos, convertir a y desde objetos y JSON, Map frente a objeto y Record, y cómo tipar array.map().

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Un Map de TypeScript es el Map integrado de JavaScript con claves y valores tipados: Map<string, number> asocia strings a números. Crea uno con new Map<K, V>() y luego usa set, get, has y delete. get devuelve V | undefined, porque puede que la clave no exista.

Los argumentos de tipo son lo que hace útil un Map en TypeScript: cada set se comprueba y cada get devuelve el tipo del valor. La última línea es un error de compilación (TS2345); @ts-expect-error mantiene el bloque en marcha.

Esta página trata de la colección Map. Si buscabas array.map(), está en la última sección.

Crear un Map

Los tipos vienen de los argumentos de tipo, de las entradas iniciales o de una anotación. Las entradas iniciales son un array de tuplas [key, value], o cualquier otra cosa que las produzca.

Dos trampas de inferencia:

  • new Map() sin argumentos de tipo ni entradas es Map<any, any>. No se comprueba nada de lo que metes ni de lo que sacas. Escribe siempre new Map<K, V>().
  • Las entradas con valores de distinto tipo no infieren una unión. new Map([["a", 1], ["b", "x"]]) es el error TS2769 (No overload matches this call). Escribe el tipo: new Map<string, number | string>([...]).

get devuelve V | undefined

Un Map no puede garantizar que una clave exista, así que get tiene tipo V | undefined. Con strict, debes ocuparte del undefined antes de usar el valor como V:

index.ts(4,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'.
  Type 'undefined' is not assignable to type 'number'.

Las soluciones, de más a menos habitual:

TypeScript no recuerda que has devolvió true cuando después llamas a get. Comprobar directamente el resultado de get es más corto y seguro. Una aserción non-null, stock.get("apples")!, silencia el error pero no protege de nada si la clave falta.

Métodos de Map y sus tipos

MiembroTipo en Map<K, V>Notas
new Map<K, V>(entries?)Map<K, V>entries: iterable de [K, V]
set(key, value)thisAñade o sustituye; encadenable
get(key)V | undefinedundefined si falta
has(key)boolean
delete(key)booleantrue si se eliminó algo
clear()voidLo elimina todo
sizenumberUna propiedad, no un método
keys(), values()iteradores de K, VExpándelos en un array: [...map.keys()]
entries(), for...ofiterador de [K, V]Orden de inserción
forEach((value, key) => ...)voidOjo: el valor va primero

Recorrer un Map

Un Map se recorre en orden de inserción. for...of sobre el map produce tuplas [key, value], de tipo [K, V].

Asignar una clave que ya existe actualiza el valor pero mantiene su posición original en el orden.

Claves que son objetos y contar elementos

Cualquier valor puede ser una clave, incluidos objetos y arrays. Las claves se comparan como con ===: dos objetos con el mismo contenido son claves distintas. Un Map también es la forma estándar de contar o agrupar elementos.

Para usar el contenido de un objeto como clave, deriva una clave string o numérica, como user.id o `${x},${y}`.

Map frente a objeto y Record

Map<K, V>Objeto / Record<string, V>
Tipos de clavecualquiera, comparada como con ===string (los números pasan a strings), symbol
Tipo si falta la claveget devuelve V | undefinedobj[key] es V salvo que noUncheckedIndexedAccess esté activado
Ordenorden de insercióncasi siempre de inserción, pero las claves que parecen enteros van primero en orden ascendente
Tamañomap.sizeObject.keys(obj).length
Añadir y borrar con frecuenciaoptimizado para ellono optimizado para ello
JSONno directamente (JSON.stringify(map) es "{}")directo
Sintaxis literal, desestructuraciónnosí
Claves heredadas por accidenteninguna"toString" in {} es true

Regla práctica: un Map para una colección cuyas claves son datos (ids de usuario, palabras, entradas de caché) y cambian en tiempo de ejecución; un tipo objeto o Record para un conjunto fijo de claves conocidas y para todo lo que va a o viene de JSON. La página de diccionarios compara las firmas de índice, Record y Map para búsquedas con claves string.

Convertir Maps en objetos y JSON

Las entradas de un Map no son propiedades, así que JSON.stringify no las ve. Convierte con Object.fromEntries y Object.entries:

JSON.parse devuelve any, así que el as Record<...> indica qué se espera que sean los datos. No es una comprobación en tiempo de ejecución; valida el JSON no fiable antes de confiar en ese tipo.

Tipar array.map()

Muchas búsquedas de «typescript map» se refieren al método de array, que transforma cada elemento y devuelve un array nuevo. Su tipo se infiere a partir del callback, así que rara vez hacen falta anotaciones:

Anotar el tipo de retorno del callback ((u): Option => ...) es la forma más clara de indicar el tipo del resultado: una propiedad que falta o está mal escrita en el objeto devuelto pasa a ser un error de compilación en el callback.

Preguntas frecuentes

¿Cómo se crea un Map en TypeScript?

Pasa los tipos de clave y valor al constructor: const ages = new Map<string, number>(). Con entradas iniciales, los tipos se infieren: new Map([["ada", 36]]) es un Map<string, number>. Un new Map() sin tipos ni entradas es Map<any, any>, que desactiva la comprobación, así que dale siempre tipos.

¿Por qué Map.get devuelve undefined en TypeScript?

map.get(key) tiene tipo V | undefined porque puede que la clave no esté. TypeScript no relaciona un map.has(key) anterior con un get posterior, así que incluso después de has tienes que manejar undefined: guarda el resultado y compruébalo, o usa un valor por defecto con ??.

¿Qué diferencia hay entre Map y un objeto en TypeScript?

Un Map acepta claves de cualquier tipo (objetos incluidos), mantiene el orden de inserción, tiene size y está pensado para añadir y borrar con frecuencia. Un objeto normal o Record<string, V> solo tiene claves string (y symbol), se serializa a JSON directamente y admite sintaxis literal y desestructuración. Usa un Map para colecciones dinámicas con clave y un objeto para formas fijas y datos JSON.

¿Cómo convierto un Map en un objeto o en JSON en TypeScript?

Object.fromEntries(map) convierte un Map<string, V> en un objeto normal, que luego JSON.stringify puede serializar. JSON.stringify(map) sobre el propio Map da "{}", porque las entradas de un Map no son propiedades. La operación inversa es new Map(Object.entries(obj)).

¿Cómo tipo el callback de array.map en TypeScript?

Normalmente no hace falta: items.map((item) => item.name) infiere item a partir del array y el tipo del resultado a partir de lo que devuelve el callback. Para forzar un tipo de resultado, pásalo como argumento de tipo, items.map<string>(...), o anota el tipo de retorno del callback.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR