JSON.stringify(value, null, 2) writes a value as JSON with each member on its own line, indented two spaces per level. When what you have is a JSON string, parse it first: JSON.stringify(JSON.parse(text), null, 2). The second argument is the replacer (null for none) and the third is the indent.
const text = '{"name":"café","id":42,"tags":["api","ops"],"active":true,"manager":null}';
console.log(JSON.stringify(JSON.parse(text), null, 2));
{
"name": "café",
"id": 42,
"tags": [
"api",
"ops"
],
"active": true,
"manager": null
}
The formatter below prints the same for that text with 2 spaces. Skip the JSON.parse and you get the string back as one quoted JSON string: "{\"name\":\"café\",\"id\":42,...}".
The indent and the replacer
The indent is a number of spaces, capped at 10, or a string such as '\t', cut to its first 10 characters. The replacer is either an array of the keys to keep or a function that sees every key and value and returns what to write; returning undefined drops the property.
const user = { id: 42, name: 'Ada', password: 'hunter2' };
JSON.stringify(user, ['id', 'name']); // '{"id":42,"name":"Ada"}'
JSON.stringify(user, (key, value) => (key === 'password' ? undefined : value)); // '{"id":42,"name":"Ada"}'
What a round trip changes
JSON.parse reads every number as an IEEE 754 double, which is exact only up to 2^53 − 1 (Number.MAX_SAFE_INTEGER, 9007199254740991). Past that, digits change:
JSON.stringify(JSON.parse('{"price":1.10,"id":12345678901234567890,"exp":1e5,"big":1e1000}'));
// '{"price":1.1,"id":12345678901234567000,"exp":100000,"big":null}'
1e1000 overflows to Infinity, and JSON.stringify writes Infinity and NaN as null. undefined, functions and symbols disappear from objects and become null in arrays, and a Date becomes its ISO string. The formatter below works on the text’s own tokens, so 1.10 and all 20 digits of that id survive there.
BigInt
JSON.stringify({ id: 1n }) throws TypeError: Do not know how to serialize a BigInt. Convert it in the replacer, to a string or, in Node 22, to a bare number with JSON.rawJSON:
const data = { id: 12345678901234567890n };
JSON.stringify(data, (k, v) => (typeof v === 'bigint' ? v.toString() : v)); // '{"id":"12345678901234567890"}'
JSON.stringify(data, (k, v) => (typeof v === 'bigint' ? JSON.rawJSON(v.toString()) : v)); // '{"id":12345678901234567890}'
Node 22 also hands a reviver the number’s source text, so big integers can be read without loss: JSON.parse(text, (k, v, { source }) => (k === 'id' ? BigInt(source) : v)). Check your browser targets before relying on either.
From the shell
node -e 'console.log(JSON.stringify(JSON.parse(require("fs").readFileSync(0, "utf8")), null, 2))' < data.json