JSON.parse y JSON.stringify en JavaScript: opciones y casos extremos

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.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP