JSON.stringify y JSON.parse son las dos funciones para serializar y deserializar JSON en JavaScript. Tienen parámetros opcionales muy útiles que pocos developers conocen: el replacer para controlar qué se serializa y cómo, el reviver para transformar los valores al parsear, y un tercer parámetro para formatear el JSON legible.
JSON.stringify con replacer e indentación
El segundo parámetro puede ser un array de claves (whitelist) o una función que transforma cada par clave/valor. El tercer parámetro controla la indentación:
const usuario = {
id: 1,
nombre: 'Ana',
password: 'secreto123', // No queremos serializar esto
saldo: 1500.50,
activo: true,
creado: new Date('2026-01-15')
};
// Replacer como array: solo incluir esas claves
JSON.stringify(usuario, ['id', 'nombre', 'activo']);
// '{"id":1,"nombre":"Ana","activo":true}'
// Replacer como función: control total
JSON.stringify(usuario, (clave, valor) => {
if (clave === 'password') return undefined; // Omitir
if (clave === 'saldo') return parseFloat(valor.toFixed(2));
return valor;
}, 2); // Indentación de 2 espacios
// {
// "id": 1,
// "nombre": "Ana",
// "saldo": 1500.50,
// "activo": true,
// "creado": "2026-01-15T00:00:00.000Z"
// }
// Indentación con tabulador:
JSON.stringify({ a: 1, b: 2 }, null, 't');
Tipos no serializables
Algunos tipos de JavaScript no tienen representación en JSON. Conocer cómo los trata stringify evita sorpresas:
const objeto = {
string: 'texto',
numero: 42,
boolean: true,
nulo: null,
indefinido: undefined, // Se omite
funcion: () => {}, // Se omite
simbolo: Symbol('s'), // Se omite
fecha: new Date(), // Convierte a string ISO
regexp: /regex/, // {} (objeto vacío)
nan: NaN, // null
infinito: Infinity, // null
mapa: new Map([[1, 2]]), // {} (no serializable)
set: new Set([1, 2, 3]) // {} (no serializable)
};
console.log(JSON.stringify(objeto));
// {"string":"texto","numero":42,"boolean":true,"nulo":null,
// "fecha":"2026-06-26T...","regexp":{},"nan":null,"infinito":null,
// "mapa":{},"set":{}}
// ¡undefined, funcion y simbolo desaparecen!
// En arrays, undefined se convierte en null:
JSON.stringify([1, undefined, 3]); // '[1,null,3]'
JSON.parse con reviver
El reviver es una función que se aplica a cada par clave/valor del JSON parseado, de dentro hacia fuera. Es ideal para restaurar tipos como fechas:
const json = '{"nombre":"Ana","nacimiento":"1995-03-15T00:00:00.000Z","puntos":150}';
// Sin reviver: las fechas llegan como string
const sinReviver = JSON.parse(json);
console.log(typeof sinReviver.nacimiento); // 'string'
console.log(sinReviver.nacimiento instanceof Date); // false
// Con reviver: restaurar fechas
const conReviver = JSON.parse(json, (clave, valor) => {
// Los campos que parecen fechas ISO, convertirlos a Date
if (typeof valor === 'string' && /^d{4}-d{2}-d{2}T/.test(valor)) {
return new Date(valor);
}
return valor;
});
console.log(conReviver.nacimiento instanceof Date); // true
console.log(conReviver.nacimiento.getFullYear()); // 1995
Manejar referencias circulares
JSON.stringify lanza un TypeError si el objeto tiene referencias circulares. Hay que manejarlas explícitamente:
// Referencia circular:
const obj = { nombre: 'nodo' };
obj.propio = obj; // obj ? obj
// JSON.stringify(obj); // TypeError: Converting circular structure to JSON
// Solución 1: replacer que detecta objetos ya visitados
function stringifySeguro(obj) {
const vistos = new WeakSet();
return JSON.stringify(obj, (clave, valor) => {
if (typeof valor === 'object' && valor !== null) {
if (vistos.has(valor)) return '[Circular]';
vistos.add(valor);
}
return valor;
});
}
const circular = { a: 1 };
circular.self = circular;
console.log(stringifySeguro(circular));
// '{"a":1,"self":"[Circular]"}'
// Solución 2: structuredClone (clona sin circulares) + stringify
// (solo si el objeto es clonable con structuredClone)
toJSON(): controlar la serialización de una clase
Si un objeto tiene un método toJSON(), JSON.stringify lo llama y serializa su resultado en lugar del objeto completo:
class Producto {
constructor(nombre, precio, costo) {
this.nombre = nombre;
this.precio = precio;
this.costo = costo; // Dato interno, no queremos exponerlo
}
toJSON() {
return {
nombre: this.nombre,
precio: this.precio.toFixed(2),
// costo: omitido intencionalmente
};
}
}
const p = new Producto('Libro', 29.9999, 15);
console.log(JSON.stringify(p));
// '{"nombre":"Libro","precio":"30.00"}'
// Date usa toJSON() internamente para devolver la ISO string
new Date().toJSON(); // '2026-06-26T...'
Para serializar tipos no soportados como Map o Set, la solución más limpia es implementar toJSON() en ellos o usar el replacer. Para deserializar de vuelta, el reviver debe hacer la transformación inversa.
