Ir al contenido
Toova
Todas las herramientas

7 Trucos JSON que Te Ahorrarán Horas

Toova

Todo desarrollador de JavaScript usa JSON.parse y JSON.stringify docenas de veces por semana. Pero la mayoría se queda en lo básico — parsear respuestas de API y serializar objetos a strings. La API tiene mucho más que ofrecer, y algunas de las funcionalidades menos usadas resuelven problemas que de otro modo requerirían librerías de terceros o horas de depuración.

Esta guía cubre siete trucos que van más allá de los valores por defecto. Cada uno es inmediatamente aplicable a codebases reales. Sin abstracciones, sin ejemplos inventados — estos son patrones que aparecen en sistemas de producción.

Puedes verificar y explorar cualquiera de los ejemplos de código en este artículo usando el Formateador JSON de Toova, que valida y formatea JSON completamente en tu navegador.

1. Deep Clone con Round-Trip JSON (y Sus Límites)

El truco más antiguo del libro de JavaScript: usar JSON.parse(JSON.stringify(obj)) para crear un deep clone de un objeto plano.

const original = { a: 1, b: { c: 2 } };

// Enfoque ingenuo — los objetos profundamente anidados aún se comparten
const shallowCopy = { ...original }; // b aún apunta al mismo objeto

// Deep clone con JSON — crea una copia completamente independiente
const deepClone = JSON.parse(JSON.stringify(original));

deepClone.b.c = 99;
console.log(original.b.c); // 2 — original sin cambios

Funciona porque serializar a un string y volver a parsear crea un árbol de objetos completamente nuevo sin referencias compartidas. Es rápido, no requiere dependencias y ha estado disponible desde ES5.

La alternativa moderna para uso en memoria es structuredClone():

// Alternativa moderna: structuredClone() — maneja más tipos
// Disponible en Node.js 17+ y todos los navegadores evergreen
const clone = structuredClone(original);

Conoce los límites del enfoque JSON antes de depender de él:

// JSON.parse/stringify NO PUEDE manejar:
const broken = {
  date: new Date(),      // se convierte en string — pierde el prototipo Date
  fn: () => 'hello',    // se descarta silenciosamente
  undef: undefined,     // se descarta silenciosamente
  inf: Infinity,        // se convierte en null
  map: new Map(),       // se convierte en {}
  cycle: null,          // las refs circulares lanzan error
};

Si tu objeto solo contiene datos planos — strings, números, booleanos, arrays y objetos planos anidados — el round-trip JSON es seguro y rápido. Para cualquier otra cosa, prefiere structuredClone() o una librería dedicada.

2. Replacer Personalizado para Filtrado y Enmascaramiento

La mayoría de los desarrolladores saben que JSON.stringify acepta un segundo argumento, pero rara vez lo usan. Ese segundo argumento es el replacer: ya sea un array de claves a incluir, o una función que controla exactamente cómo se serializa cada valor.

Replacer como array — lista blanca de claves específicas:

const user = {
  id: 'u_001',
  name: 'Alice',
  password: 'hunter2',        // no debe aparecer en los logs
  creditCard: '4111111111111111', // no debe aparecer en los logs
  role: 'admin',
};

// Replacer como array: incluye solo estas claves
JSON.stringify(user, ['id', 'name', 'role']);
// '{"id":"u_001","name":"Alice","role":"admin"}'

Replacer como función — transforma o redacta valores:

// Replacer como función: control total sobre clave/valor
const masked = JSON.stringify(user, (key, value) => {
  if (key === 'password' || key === 'creditCard') return '[REDACTED]';
  if (key === '' ) return value; // objeto raíz — siempre retornar
  return value;
});
// '{"id":"u_001","name":"Alice","password":"[REDACTED]","creditCard":"[REDACTED]","role":"admin"}'

Una versión más sofisticada enmascara valores según su forma en lugar de su nombre de clave:

// Replacer para enmascaramiento basado en tipo
const sanitize = (key, value) => {
  if (typeof value === 'string' && value.match(/^4[0-9]{15}$/)) {
    return '****-****-****-' + value.slice(-4);
  }
  return value;
};

Esta técnica es indispensable para middleware de logging: quieres logs estructurados con contexto completo del objeto, pero ciertos campos nunca deben llegar a un agregador de logs. El replacer te permite manejar esto en el límite de serialización en lugar de dispersar la lógica de redacción por todo el codebase.

3. Ordenar Claves de Forma Determinista

El orden de claves de un objeto en JavaScript es el orden de inserción (para claves de tipo string). Dos objetos con las mismas claves pero creados en diferente orden producen strings JSON diferentes, lo que rompe las verificaciones de igualdad ingenuas, las claves de caché y los hashes de contenido.

function sortedStringify(obj) {
  return JSON.stringify(obj, Object.keys(obj).sort());
}

const a = { z: 1, a: 2, m: 3 };
const b = { a: 2, m: 3, z: 1 };

sortedStringify(a) === sortedStringify(b); // true — orden de claves normalizado

Para objetos profundamente anidados, aplica el ordenamiento recursivamente:

// Ordenamiento recursivo de claves para objetos anidados
function sortKeys(value) {
  if (Array.isArray(value)) return value.map(sortKeys);
  if (value !== null && typeof value === 'object') {
    return Object.fromEntries(
      Object.keys(value).sort().map((k) => [k, sortKeys(value[k])])
    );
  }
  return value;
}

const sorted = JSON.stringify(sortKeys(deepNested));

El JSON ordenado es esencial cuando:

  • Generas claves de caché a partir de cuerpos de solicitud
  • Calculas checksums o firmas sobre payloads JSON
  • Comparas respuestas de API en tests independientemente del orden de los campos
  • Almacenas objetos de configuración donde el orden no debería afectar la igualdad

Después de ordenar y formatear, usa la herramienta Text Diff para comparar dos strings JSON normalizados y ver exactamente qué valores cambiaron entre versiones.

4. Manejo de BigInt Sin Perder Precisión

El tipo Number de JavaScript puede representar de forma segura enteros hasta 253 − 1. Para IDs generados por sistemas distribuidos, montos financieros en unidades menores, o timestamps en nanosegundos, esto no es suficiente. BigInt cubre enteros de precisión arbitraria, pero JSON.stringify no sabe qué hacer con ellos.

const data = {
  amount: 9007199254740993n, // mayor que Number.MAX_SAFE_INTEGER
};

// Esto lanza: TypeError: Do not know how to serialize a BigInt
JSON.stringify(data); // ERROR

Las soluciones estándar:

// Solución 1: Convertir a string con un replacer
JSON.stringify(data, (key, value) =>
  typeof value === 'bigint' ? value.toString() : value
);
// '{"amount":"9007199254740993"}'

// Solución 2: toJSON() en el prototipo BigInt (monkey-patch — usar con precaución)
BigInt.prototype.toJSON = function () { return this.toString(); };
JSON.stringify(data); // '{"amount":"9007199254740993"}'

En el lado del parsing, una función reviver puede restaurar los valores BigInt desde su representación en string:

// Reviver para restaurar BigInt al parsear
const revived = JSON.parse('{"amount":"9007199254740993"}', (key, value) => {
  if (key === 'amount') return BigInt(value);
  return value;
});
console.log(typeof revived.amount); // 'bigint'

Mantén la conversión BigInt-a-string en una capa de serialización compartida para que se aplique de forma consistente. Dejar que los BigInts se filtren hacia llamadas ad hoc a JSON.stringify en los bordes de un codebase lleva a errores impredecibles que son difíciles de rastrear.

5. Detección de Referencias Circulares

Una referencia circular ocurre cuando un objeto contiene una referencia a sí mismo o a un ancestro en el grafo de objetos. Son más comunes de lo que podrías pensar: emisores de eventos, nodos DOM, nodos fiber de React y entidades ORM frecuentemente tienen referencias inversas.

// Ejemplo de referencia circular
const obj = { name: 'node' };
obj.self = obj; // obj se referencia a sí mismo

JSON.stringify(obj); // lanza: TypeError: Converting circular structure to JSON

Manéjalo con un replacer personalizado que rastrea los objetos visitados:

// Manejador manual de referencias circulares
function safeStringify(obj) {
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) return '[Circular]';
      seen.add(value);
    }
    return value;
  });
}

safeStringify(obj); // '{"name":"node","self":"[Circular]"}'

El WeakSet es la estructura de datos correcta aquí: mantiene referencias a objetos sin impedir la recolección de basura, y la búsqueda es O(1). Este patrón también funciona en middleware de logging donde quieres que los errores degraden graciosamente en lugar de lanzar durante la serialización.

Una variación común: en lugar de marcar las referencias circulares como [Circular], reemplázalas con una cadena de ruta como [Circular: $.config.parent] para hacer la ubicación de la referencia explícita durante la depuración.

6. Pretty-Print con Arrays en Una Sola Línea

El tercer argumento de JSON.stringify es el indent. Pasar 2 o 4 expande todo a múltiples líneas, lo cual es genial para objetos pero puede ser verboso para arrays de primitivos como etiquetas, IDs o coordenadas.

const mixed = {
  title: 'Reporte',
  tags: ['json', 'api', 'debug'],
  config: { indent: 2, sortKeys: true },
  count: 42,
};

// Por defecto: todo en múltiples líneas
JSON.stringify(mixed, null, 2);
// {
//   "title": "Reporte",
//   "tags": [
//     "json",
//     "api",
//     "debug"
//   ],
//   "config": {
//     "indent": 2,
//     "sortKeys": true
//   },
//   "count": 42
// }

Puedes post-procesar la salida para colapsar arrays simples en una sola línea:

// Replacer personalizado para arrays en una sola línea
function prettyMixed(obj) {
  const raw = JSON.stringify(obj, null, 2);
  // Colapsa arrays que contienen solo primitivos en una línea
  return raw.replace(
    /\[\n\s+([\s\S]*?)\n\s+\]/g,
    (match, inner) => {
      const items = inner.split(',\n').map((s) => s.trim());
      if (items.every((s) => !/^[{\[]/.test(s))) {
        return '[' + items.join(', ') + ']';
      }
      return match;
    }
  );
}

El resultado: los objetos permanecen en múltiples líneas para mayor legibilidad, los arrays de primitivos se colapsan a una línea para mayor compacidad. Este es el formato usado en muchos archivos de configuración y salidas de logs estructurados donde tanto humanos como máquinas necesitan leer los mismos datos. El Formateador JSON y el convertidor JSON a YAML manejan este colapso automáticamente.

7. JSON en Streaming para Archivos Grandes

Cargar un archivo JSON grande completamente en memoria antes de parsearlo es el error de rendimiento más común en los pipelines de procesamiento de JSON. Una respuesta JSON de 100 MB asigna al menos 100 MB de heap para el string raw, luego una segunda asignación para el objeto parseado. Para archivos de más de unos pocos megabytes, un parser en streaming procesa los datos de forma incremental.

En Node.js con una librería de streaming:

// node --experimental-vm-modules (o usar una librería de JSON en streaming)
// Streaming nativo con la propuesta JSON Source Map (Stage 3, 2026):

// Por ahora, usa una librería como @streamparser/json o jsonstream2
import { createReadStream } from 'fs';
import parser from '@streamparser/json';

const jsonParser = new parser.JSONParser();
jsonParser.onValue = ({ value, key, parent }) => {
  if (key === 'id') {
    console.log('ID encontrado:', value);
  }
};

createReadStream('large.json').pipe(jsonParser);

En el navegador usando la API Fetch Streams:

// Streaming en el navegador con Fetch + decodificador de streams JSON:
const response = await fetch('/api/large-data');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  // Procesa objetos JSON completos del buffer...
}

El streaming es más útil en pipelines de procesamiento por lotes, grandes endpoints de exportación y scripts de análisis de logs. Para respuestas de API típicas de menos de 1 MB, el JSON.parse estándar es lo suficientemente rápido y mucho más simple. El umbral donde el streaming empieza a ser rentable en la práctica es alrededor de 5–10 MB, dependiendo de la complejidad de la estructura parseada y el tamaño del heap del entorno objetivo.

Para explorar la estructura de respuestas JSON grandes antes de construir un parser en streaming, pega una muestra en el Formateador JSON para ver qué rutas contienen los datos que necesitas, luego convierte a CSV o YAML para un análisis adicional.

Poniéndolo Todo Junto

Estas siete técnicas cubren los rincones más impactantes de la API JSON:

  • Deep clone vía round-trip — rápido para datos planos, conoce los límites
  • Replacer — filtra claves y enmascara valores sensibles en el límite de serialización
  • Ordenamiento de claves — salida determinista para claves de caché, firmas y aserciones de tests
  • Manejo de BigInt — serializa a string, revive al parsear
  • Detección de referencias circulares — replacer basado en WeakSet para serialización segura
  • Pretty-print con arrays compactos — salida legible para humanos sin exceso de espacios en blanco
  • JSON en streaming para archivos grandes — parsing incremental para archivos grandes y respuestas de API

Para la documentación completa del replacer y los argumentos de espacio de JSON.stringify, consulta la referencia MDN de JSON.stringify. Para la alternativa moderna de deep clone, consulta la documentación de structuredClone(), que cubre todos los tipos soportados y casos límite.