Formatear JSON en Node.js: JSON.stringify, util.inspect y la trampa del BigInt
Formatear JSON en Node.js es la línea de siempre. Lo interesante es cuándo conviene tirar de util.inspect, qué hace tropezar a JSON.stringify con payloads reales, y cómo escribir salida determinista a disco sin librerías externas.
JSON.stringify con indent es suficiente
JSON.stringify(obj, null, 2) es la respuesta correcta en el noventa por ciento de los casos. El tercer argumento es la indentación. Pasa 2 para dos espacios, pasa un string de tab para tabs, pasa nada para la línea infinita que nadie puede leer en un log.
El segundo argumento es el replacer. Pasa null si no hay transformación. Pasa una función para redactar secretos o convertir valores. Pasa un array de claves para filtrar propiedades. La gente olvida que existe y acaba escribiendo pasos de post-proceso que la propia API ya hacía.
Cuándo saltarse util.inspect
util.inspect de node:util está pensado para ojos humanos en una terminal. Usa comillas simples, imprime sin expandir, colorea por defecto, y muestra referencias circulares como [Circular] en vez de lanzar excepción. Va perfecto para logs de desarrollo.
No es JSON. La salida no se puede parsear de vuelta. Si logueas la salida de inspect y un sistema downstream intenta leerla como JSON, el parse peta. Regla práctica: util.inspect para la persona delante del teclado, JSON.stringify para cualquier consumidor que no sea humano.
BigInt, refs circulares y Date
Tres problemas reales.
BigInt lanza TypeError: Do not know how to serialize a BigInt. Conviértelo en el replacer: (_, v) => typeof v === "bigint" ? v.toString() : v. Marca en el contrato si el consumidor necesita saber que era BigInt.
Las referencias circulares lanzan Converting circular structure to JSON. Lleva un Set de objetos vistos dentro del replacer y rompe el ciclo. util.inspect las tolera, JSON.stringify no.
Los Date se convierten a string ISO vía toJSON. Suele ser lo que quieres, pero si necesitas un timestamp Unix, sobrescribe en el replacer o llama a getTime antes.
Escribir JSON bonito a disco
Usa node:fs/promises con writeFile. Para salida determinista ordena las claves antes de stringify. Node no trae un flag sort_keys nativo, así que se construye con Object.keys().sort() y reduce. El ejemplo de abajo produce los mismos bytes para la misma entrada, siempre, que es lo que hace útiles los diffs y los hashes de contenido.
Ejemplo completo
javascriptimport { writeFile } from "node:fs/promises";
const config = {
port: 3000,
database: { host: "localhost", port: 5432 },
features: ["auth", "billing"],
createdAt: new Date(),
};
// Orden recursivo para imitar sort_keys=True de Python
function sortKeys(value) {
if (Array.isArray(value)) return value.map(sortKeys);
if (value && typeof value === "object" && !(value instanceof Date)) {
return Object.keys(value)
.sort()
.reduce((acc, k) => {
acc[k] = sortKeys(value[k]);
return acc;
}, {});
}
return value;
}
const formatted = JSON.stringify(sortKeys(config), null, 2);
await writeFile("config.json", formatted, "utf8"); ¿Solo necesitas el resultado?
Cuando tienes un payload que sale de un servicio Node y quieres inspeccionarlo sin abrir un REPL, pégalo en el formateador JSON de aldeacode.com. Corre entero en el navegador, no sube nada, y te devuelve JSON válido que vuelve a parsear sin sorpresas.
Abrir Formateador y Validador JSON →Preguntas frecuentes
¿Mejor node:fs/promises o la API de callbacks?
Promises. La API de callbacks sigue ahí pero el módulo de promises es el default moderno en Node 18+ y casa bien con await en top level cuando tienes type:module en el package.json.
¿Es seguro usar util.inspect.colors en logs de producción?
Solo si el destino es una TTY. Si pipeas un stream con color a un agregador de logs acabas con códigos ANSI mezclados en el texto indexado. Detecta con process.stdout.isTTY y desactiva el color cuando sea false.
¿Cómo hago streaming de objetos grandes sin cargar todo?
JSON.stringify construye el string completo en memoria. Para payloads de varios gigabytes usa un serializador en streaming tipo JSONStream o escribe cada item de top level con JSON.stringify manual y una coma entre llamadas.