Tema
Storage y JSON
¿Recargaste la página y tu lista de tareas desapareció? Las variables de JavaScript viven en memoria y se borran al cerrar o recargar la pestaña. En esta lección aprenderás a guardar datos en el navegador con localStorage y sessionStorage, y a convertir objetos en texto con JSON, el formato con el que se comunican prácticamente todas las APIs del mundo.
Concepto
JSON
JSON (JavaScript Object Notation) es un formato de texto para representar datos. Se parece muchísimo a los objetos de JavaScript, pero es sólo texto: puede viajar por internet, guardarse en un archivo o en el storage.
Piensa en JSON como empacar una mudanza: tus objetos (muebles) se desarman y se meten en cajas (texto) para transportarlos; al llegar se vuelven a armar.
json
{
"id": 1,
"nombre": "Ana",
"activo": true,
"intereses": ["diseño", "café"],
"direccion": { "ciudad": "CDMX" },
"telefono": null
}Reglas de JSON (más estrictas que un objeto JS):
- Las claves siempre van entre comillas dobles.
- Los strings sólo con comillas dobles (
"hola", nunca'hola'). - Sin comas al final del último elemento.
- Sin funciones,
undefined, fechas (Date) ni comentarios. - Valores permitidos: string, número, booleano,
null, array y objeto.
Web Storage
El navegador te da dos "cajones" para guardar texto por cada sitio (dominio):
localStorage | sessionStorage | |
|---|---|---|
| Duración | Permanente (hasta que se borre) | Mientras la pestaña esté abierta |
| Compartido entre pestañas | Sí | No, cada pestaña tiene el suyo |
| Capacidad aproximada | ~5 MB | ~5 MB |
| Uso típico | Preferencias, carrito, borradores | Datos de un proceso de varios pasos |
JSON.stringify y JSON.parse
js
const usuario = {
nombre: 'Ana',
edad: 28,
intereses: ['diseño', 'café'],
saludar() { return 'hola'; }, // las funciones se ignoran
apodo: undefined // undefined se ignora
};
// Objeto → texto JSON
const texto = JSON.stringify(usuario);
// '{"nombre":"Ana","edad":28,"intereses":["diseño","café"]}'
// Con formato legible (2 espacios de sangría), útil para depurar
console.log(JSON.stringify(usuario, null, 2));
// Sólo algunas propiedades
JSON.stringify(usuario, ['nombre', 'edad']); // '{"nombre":"Ana","edad":28}'
// Texto JSON → objeto
const deVuelta = JSON.parse(texto);
console.log(deVuelta.intereses[0]); // 'diseño'Fechas en JSON
js
const pedido = { id: 1, fecha: new Date('2026-10-18T10:00:00Z') };
const json = JSON.stringify(pedido);
// '{"id":1,"fecha":"2026-10-18T10:00:00.000Z"}' ← la fecha se volvió string
const recuperado = JSON.parse(json);
typeof recuperado.fecha; // 'string'
const fecha = new Date(recuperado.fecha); // vuelve a ser DateJSON inválido: siempre con try/catch
JSON.parse lanza un error si el texto no es JSON válido, y eso detiene tu programa:
js
JSON.parse("{nombre: 'Ana'}"); // ❌ SyntaxError: claves sin comillas dobles
JSON.parse(''); // ❌ SyntaxError
JSON.parse('null'); // ✅ null
function parsearSeguro(texto, valorPorDefecto = null) {
try {
return JSON.parse(texto) ?? valorPorDefecto;
} catch {
return valorPorDefecto;
}
}
parsearSeguro('esto no es json', []); // []localStorage: lo básico
js
// Guardar (clave y valor SIEMPRE se guardan como string)
localStorage.setItem('tema', 'oscuro');
// Leer (devuelve null si la clave no existe)
const tema = localStorage.getItem('tema'); // 'oscuro'
// Eliminar una clave
localStorage.removeItem('tema');
// Borrar TODO lo de tu sitio
localStorage.clear();
// Cuántas claves hay y recorrerlas
for (let i = 0; i < localStorage.length; i++) {
const clave = localStorage.key(i);
console.log(clave, localStorage.getItem(clave));
}sessionStorage tiene exactamente los mismos métodos.
Error común: guardar objetos sin JSON
js
localStorage.setItem('usuario', { nombre: 'Ana' });
localStorage.getItem('usuario'); // '[object Object]' 😱Todo se convierte a string. Para objetos y arrays usa JSON.stringify al guardar y JSON.parse al leer.
Lo mismo con números y booleanos: localStorage.getItem('visitas') devuelve '5' (string), y 'false' es un string truthy.
Guardar objetos y arrays
js
const carrito = [
{ id: 2, nombre: 'Mouse', precio: 349, cantidad: 2 },
{ id: 5, nombre: 'Audífonos', precio: 899, cantidad: 1 }
];
// Guardar
localStorage.setItem('carrito', JSON.stringify(carrito));
// Leer (con valor por defecto si no existe)
const guardado = JSON.parse(localStorage.getItem('carrito')) ?? [];Un módulo de storage seguro y reutilizable
En proyectos reales, localStorage puede fallar: navegación privada en algunos navegadores, almacenamiento lleno (QuotaExceededError), cookies bloqueadas, o datos corruptos que alguien editó a mano en DevTools. Envuelve todo en try/catch una sola vez:
js
// storage.js
const PREFIJO = 'miapp:'; // evita choques con otras apps del mismo dominio
export const storage = {
get(clave, valorPorDefecto = null) {
try {
const texto = localStorage.getItem(PREFIJO + clave);
return texto === null ? valorPorDefecto : JSON.parse(texto);
} catch (error) {
console.warn(`No se pudo leer "${clave}"`, error);
return valorPorDefecto;
}
},
set(clave, valor) {
try {
localStorage.setItem(PREFIJO + clave, JSON.stringify(valor));
return true;
} catch (error) {
console.warn(`No se pudo guardar "${clave}"`, error);
return false;
}
},
remove(clave) {
try {
localStorage.removeItem(PREFIJO + clave);
} catch {
/* ignorar */
}
}
};
// Uso
storage.set('preferencias', { tema: 'oscuro', idioma: 'es' });
const prefs = storage.get('preferencias', { tema: 'claro', idioma: 'es' });Buena práctica: versiona tus datos
Si mañana cambias la estructura (por ejemplo, de precio a precioCentavos), los datos viejos del navegador de tus usuarios romperán la app. Guarda una versión: { version: 2, items: [...] } y migra o descarta si no coincide.
Caso real: tema oscuro que se recuerda
js
const boton = document.querySelector('#toggle-tema');
// 1. Al cargar: preferencia guardada o la del sistema operativo
const guardado = storage.get('tema');
const prefiereOscuro = window.matchMedia('(prefers-color-scheme: dark)').matches;
let tema = guardado ?? (prefiereOscuro ? 'oscuro' : 'claro');
function aplicarTema() {
document.documentElement.dataset.tema = tema; // <html data-tema="oscuro">
boton.textContent = tema === 'oscuro' ? '☀️ Modo claro' : '🌙 Modo oscuro';
}
boton.addEventListener('click', () => {
tema = tema === 'oscuro' ? 'claro' : 'oscuro';
storage.set('tema', tema);
aplicarTema();
});
aplicarTema();css
:root { --fondo: #ffffff; --texto: #0f172a; }
[data-tema="oscuro"] { --fondo: #0f172a; --texto: #e2e8f0; }
body { background: var(--fondo); color: var(--texto); }Caso real: borrador de formulario
Si el usuario cierra la pestaña por accidente, recupera lo que estaba escribiendo:
js
const form = document.querySelector('#contacto');
// Restaurar
const borrador = storage.get('borrador-contacto', {});
for (const [nombre, valor] of Object.entries(borrador)) {
if (form.elements[nombre]) form.elements[nombre].value = valor;
}
// Guardar mientras escribe
form.addEventListener('input', () => {
storage.set('borrador-contacto', Object.fromEntries(new FormData(form)));
});
// Limpiar al enviar
form.addEventListener('submit', () => storage.remove('borrador-contacto'));Sincronizar entre pestañas
El evento storage se dispara en las otras pestañas del mismo sitio cuando cambia localStorage:
js
window.addEventListener('storage', (e) => {
if (e.key === 'miapp:carrito') {
carrito = JSON.parse(e.newValue) ?? [];
renderCarrito(); // el carrito se actualiza en todas las pestañas
}
});sessionStorage: procesos de varios pasos
js
// Paso 1 del checkout: guardar la dirección
sessionStorage.setItem('checkout-direccion', JSON.stringify({ cp: '44100', calle: 'Juárez 123' }));
// Paso 2: leerla. Si el usuario cierra la pestaña, se descarta sola.
const direccion = JSON.parse(sessionStorage.getItem('checkout-direccion'));Cookies vs. storage: ¿qué uso?
| Cookies | localStorage / sessionStorage | |
|---|---|---|
| Se envían al servidor | Sí, en cada petición | No |
| Capacidad | ~4 KB | ~5 MB |
| Accesibles desde JS | Sí (excepto HttpOnly) | Sí, siempre |
| Expiración | Configurable | Permanente / al cerrar pestaña |
| Uso ideal | Sesión de login (con HttpOnly y Secure) | Preferencias, carrito, caché, borradores |
Seguridad
Nunca guardes contraseñas, datos de tarjetas ni información sensible en localStorage. Cualquier script que se ejecute en tu página (incluido uno inyectado por XSS o una librería comprometida) puede leerlo. Los tokens de sesión, idealmente, en cookies HttpOnly gestionadas por el servidor.
Ejercicios
- Guarda en
localStoragecuántas veces el usuario ha visitado la página y muéstralo: "Esta es tu visita número 5". - Escribe una función
guardarFavorito(id)que agregue un id a un array de favoritos en storage sin duplicarlo, yesFavorito(id). - Convierte este texto JSON en objeto y muestra el nombre del segundo producto:
'{"productos":[{"nombre":"Mouse"},{"nombre":"Teclado"}]}'. - Haz que un
<textarea>guarde automáticamente su contenido y lo restaure al recargar. - Escribe
limpiarCaducados(): guarda elementos como{ valor, expira: timestamp }y elimina los que ya expiraron.
Ver solución
js
// 1
const visitas = Number(localStorage.getItem('visitas') ?? 0) + 1;
localStorage.setItem('visitas', visitas);
document.querySelector('#visitas').textContent = `Esta es tu visita número ${visitas}`;
// 2
function guardarFavorito(id) {
const favoritos = storage.get('favoritos', []);
if (!favoritos.includes(id)) storage.set('favoritos', [...favoritos, id]);
}
const esFavorito = (id) => storage.get('favoritos', []).includes(id);
// 3
const datos = JSON.parse('{"productos":[{"nombre":"Mouse"},{"nombre":"Teclado"}]}');
console.log(datos.productos[1].nombre); // 'Teclado'
// 4
const area = document.querySelector('#notas');
area.value = localStorage.getItem('notas') ?? '';
area.addEventListener('input', () => localStorage.setItem('notas', area.value));
// 5
function guardarConExpiracion(clave, valor, minutos) {
storage.set(clave, { valor, expira: Date.now() + minutos * 60000 });
}
function limpiarCaducados() {
Object.keys(localStorage)
.filter((k) => k.startsWith('miapp:'))
.forEach((k) => {
const item = storage.get(k.replace('miapp:', ''));
if (item?.expira && item.expira < Date.now()) localStorage.removeItem(k);
});
}Resumen
- JSON es texto con reglas estrictas: comillas dobles, sin funciones ni
undefined. JSON.stringifyconvierte objetos en texto;JSON.parsehace lo contrario (envuélvelo entry/catch).localStoragepersiste para siempre;sessionStoragesólo mientras la pestaña está abierta. Ambos guardan sólo strings.- Crea un pequeño módulo de storage con prefijo, valores por defecto y
try/catch. - No guardes información sensible en el storage.
Siguiente: Asincronía