The fastest way to format JSON in JavaScript is JSON.stringify(value, null, 2). The third argument, the space parameter, tells the engine to indent each nested level, turning a single dense line into readable, pretty-printed output. That one-liner covers most cases, but it breaks on circular references, drops functions and undefined silently, and gives you no control over which fields ship. This guide walks the full code path and the sharp edges every tutorial skips.
If you just need clean output once and do not want to write a throwaway script, you can paste the raw text into a free online JSON formatter and copy the result. For everything that has to happen inside your code, the sections below cover the arguments, the gotchas, and the patterns that actually matter in production.
How to Format JSON in JavaScript With JSON.stringify#
JSON.stringify() takes three arguments: the value to serialize, an optional replacer, and an optional space. To pretty-print, you skip the replacer with null and pass a number or string as space.
const user = { name: "Ada", roles: ["admin", "editor"], active: true };
// Compact (default), everything on one line
JSON.stringify(user);
// {"name":"Ada","roles":["admin","editor"],"active":true}
// Pretty-printed with 2-space indentation
JSON.stringify(user, null, 2);
// {
// "name": "Ada",
// "roles": [
// "admin",
// "editor"
// ],
// "active": true
// }
The space argument is what does the work. Pass a number from 1 to 10 for that many spaces of indent per level, or pass a string (like "\t") to indent with that string instead.
Numbers above 10 are clamped to 10.
JSON.stringify(obj, null, 50)behaves exactly likeJSON.stringify(obj, null, 10), so do not expect deeper indentation by passing a bigger number.
The space parameter: numbers vs strings#
The space argument accepts two types, and the type changes the output style:
space value | Result | Common use |
|---|---|---|
2 | Two spaces per level | JavaScript, JSON config, most web ecosystems |
4 | Four spaces per level | Python tooling, some Java/.NET outputs |
"\t" | One tab per level | Repos that enforce tab indentation |
0, null, or omitted | No formatting, single line | API payloads, storage, network transfer |
A string space lets you do things a number cannot. Pass " " (two spaces) for the same effect as 2, or pass something visual like "--" to debug nesting depth. Only the first 10 characters of a string space are used.
Why 2 vs 4 spaces actually matters#
This is not bikeshedding. Indentation width is an ecosystem convention, and matching it keeps your output consistent with the tools that will read it next.
- 2 spaces is the de facto standard in the JavaScript and Node world. Prettier defaults to 2, npm's
package.jsonuses 2, and most JSON config files you will touch (tsconfig.json,.eslintrc.json) follow suit. - 4 spaces shows up where the surrounding language uses 4, most notably Python's
json.dumps(obj, indent=4). If a JSON file lives next to Python code or is generated by a Python service, 4 keeps it visually aligned. - Tabs appear in repos with a tab-based style guide. The win is that each developer can set their own tab display width.
If your project runs Prettier or a formatter on commit, match its setting so you are not fighting the auto-formatter. When in doubt, use 2.
Using a Replacer Function to Control Output#
The second argument, replacer, is the most underused feature of JSON.stringify. It controls which properties get serialized and how their values are transformed. It accepts either an array or a function.
Replacer as an allowlist array#
Pass an array of property-name strings and only those keys survive. This is the cleanest way to strip sensitive or noisy fields before logging or sending data.
const account = {
id: 42,
email: "[email protected]",
passwordHash: "$2b$10$...",
sessionToken: "abc123",
};
// Only id and email make it into the output
JSON.stringify(account, ["id", "email"], 2);
// {
// "id": 42,
// "email": "[email protected]"
// }
The allowlist applies at every level of the object, so a key named id is kept wherever it appears in the tree. That is a feature for uniform shapes and a trap for trees where the same key name means different things at different depths.
Replacer as a transform function#
Pass a function and it runs for every key/value pair. Return the value you want serialized, return undefined to drop the key, or transform on the fly. The function receives the key and the value, and this is bound to the object holding the key.
const order = { total: 19.99, secret: "hide-me", createdAt: new Date() };
const json = JSON.stringify(order, (key, value) => {
if (key === "secret") return undefined; // drop the field
if (typeof value === "number") return value.toFixed(2); // format numbers
return value;
}, 2);
Watch the call order: the replacer is invoked first with an empty key and the whole object, then for each property. If you transform values blindly without checking the key, you can accidentally mangle the root.
A cleaner alternative for per-object control is the toJSON() method. Any object with a toJSON() method (like Date, which is why dates serialize to ISO strings) controls its own serialized form before the replacer even sees it.
Fixing the Circular Reference TypeError#
This is the error that every basic tutorial ignores, and the one that will actually stop you cold. If an object references itself, directly or through a chain, JSON.stringify throws:
TypeError: Converting circular structure to JSON
It happens constantly with DOM nodes, ORM entities that link back to their parent, event objects, and any graph-shaped data.
const node = { name: "root" };
node.self = node; // circular
JSON.stringify(node, null, 2); // TypeError: Converting circular structure to JSON
The fix is a replacer that tracks objects it has already seen and skips repeats:
function safeStringify(value, space = 2) {
const seen = new WeakSet();
return JSON.stringify(value, (key, val) => {
if (typeof val === "object" && val !== null) {
if (seen.has(val)) return "[Circular]";
seen.add(val);
}
return val;
}, space);
}
safeStringify(node);
// {
// "name": "root",
// "self": "[Circular]"
// }
A WeakSet is the right tool here because it does not prevent garbage collection of the objects it tracks. If you are on a recent Node or modern browser, the global structuredClone will also throw on circular data, so a seen-set replacer remains the practical workaround for serialization.
Values that silently disappear#
JSON.stringify does not just throw on circular data. It quietly drops or changes several value types, which leads to confusing bugs where a field "vanishes."
- Functions and
undefinedare omitted entirely from objects, and becomenullinside arrays. NaNandInfinityserialize tonull.BigIntthrows aTypeError. You must convert it to a string or number first.Symbolkeys and values are ignored.Datebecomes an ISO string (via itstoJSON), so it does not round-trip back to aDateon parse.
If a property is missing from your output, one of these is almost always why.
How to Minify JSON in JavaScript#
Minifying is the inverse of pretty-printing: strip all the whitespace for the smallest possible payload. You minify by calling JSON.stringify with no space argument (or 0).
const data = { name: "Ada", roles: ["admin", "editor"] };
JSON.stringify(data);
// {"name":"Ada","roles":["admin","editor"]}
To round-trip from pretty to minified, parse the formatted string back into an object and stringify it again without space:
const pretty = `{
"name": "Ada",
"active": true
}`;
const minified = JSON.stringify(JSON.parse(pretty));
// {"name":"Ada","active":true}
That JSON.parse then JSON.stringify pattern is also the simplest way to reformat untrusted, badly-indented JSON: parse normalizes it, and stringify re-emits it with the spacing you choose. The catch is that JSON.parse is strict, so trailing commas, single quotes, comments, or an accidental HTML response will throw a SyntaxError before you ever get to format anything.
When to Skip the Code Entirely#
Writing JSON.stringify is the right move when formatting happens inside your application: logging, building config files at runtime, normalizing an API response. But plenty of times you just have a blob of ugly JSON in your clipboard and need it readable right now. Opening a REPL, parsing, stringifying, and copying back is slower than it should be.
For that, a browser-based formatter is faster and safer:
- It validates as it formats, so malformed JSON gets a clear error with a line and column instead of a cryptic throw.
- You toggle 2-space, 4-space, tab, or minified output without rewriting code.
- It handles huge payloads that would clutter your console.
- Good ones run entirely client-side, so your data never leaves the browser.
The Molixa JSON formatter and validator does exactly this: paste, get pretty-printed and validated output, switch indentation, and copy. If you want the deeper case for keeping one in your daily toolkit, our breakdown of why a JSON formatter is a daily developer tool walks through the workflow. And when the output you are formatting needs to become typed code, the JSON to TypeScript interface guide shows how to turn a clean payload into real interfaces.
How to Build a Reusable JSON Formatter Function#
If you format JSON in several places, wrap the safe pattern once and reuse it instead of scattering JSON.stringify(obj, null, 2) across the codebase. Follow these steps to build one helper that pretty-prints, minifies, handles tabs, and never throws.
Step 1: Set up a seen-set to catch circular references#
Start with a WeakSet to track objects you have already serialized. A WeakSet is the right choice because it holds weak references and will not block garbage collection of those objects.
function formatJSON(value, { indent = 2, safe = true } = {}) {
const seen = new WeakSet();
// replacer comes next
}
The options object with defaults (indent = 2, safe = true) means a bare formatJSON(data) call does the sensible thing: 2-space, circular-safe output.
Step 2: Write a replacer that handles the unsupported types#
Add a replacer that returns a placeholder for repeated objects and converts BigInt to a string so it does not throw. This is the same circular-reference fix from earlier, plus the BigInt guard.
const replacer = safe
? (key, val) => {
if (typeof val === "object" && val !== null) {
if (seen.has(val)) return "[Circular]";
seen.add(val);
}
if (typeof val === "bigint") return val.toString();
return val;
}
: null;
When safe is false, the replacer is null, so you get plain JSON.stringify behavior for cases where you know the data is clean and want maximum speed.
Step 3: Stringify inside a try/catch so it never throws#
Wrap the JSON.stringify call so any remaining serialization error returns a readable message instead of crashing your logging or rendering path.
try {
return JSON.stringify(value, replacer, indent);
} catch (err) {
return `// Could not format: ${err.message}`;
}
}
Step 4: Call it with the indentation you need#
The same helper now covers every formatting mode through the indent option.
formatJSON(someApiResponse); // 2-space, safe
formatJSON(config, { indent: "\t" }); // tab-indented
formatJSON(payload, { indent: 0 }); // minified
Drop this in a utils file and you have pretty-printing, minification, tab indentation, and the circular-reference and BigInt traps solved in one place.
Formatting JSON in JavaScript: The Short Version#
To format JSON in JavaScript, reach for JSON.stringify(value, null, 2) first. It is the right tool nine times out of ten. Remember the three things tutorials skip: a replacer array is a clean allowlist for stripping sensitive fields, a seen-set replacer fixes the circular-reference TypeError, and several value types (functions, undefined, BigInt, NaN) get dropped or throw rather than serialize.
When the JSON lives in your clipboard rather than your code, skip the script and paste it into a JSON formatter and validator for instant pretty-printing, validation, and one-click minify. Match your indentation (usually 2 spaces) to your ecosystem, keep the reusable helper handy, and you will never wrestle with unreadable JSON again.
Frequently Asked Questions#
How do I pretty-print JSON in JavaScript?
Use JSON.stringify(value, null, 2). The third argument is the space parameter: passing 2 indents each nested level with two spaces, producing readable output. Pass 4 for four spaces or "\t" for tab indentation instead.
What does the third argument in JSON.stringify do?
The third argument is space, which controls indentation. A number from 1 to 10 indents that many spaces per level, and a string uses that string as the indent (only the first 10 characters count). Omitting it, or passing 0 or null, produces compact single-line output with no formatting.
Why does JSON.stringify throw "Converting circular structure to JSON"?
Because an object references itself directly or through a chain, and JSON has no way to represent that loop. Fix it with a replacer function that tracks already-seen objects in a WeakSet and returns a placeholder like "[Circular]" for repeats, instead of letting the engine recurse forever.
How do I minify JSON in JavaScript?
Call JSON.stringify(value) with no space argument, or pass 0. To minify an existing formatted string, run JSON.stringify(JSON.parse(prettyString)), which parses it back to an object and re-emits it with no whitespace.
Why do some properties disappear when I stringify an object?
JSON.stringify silently drops functions, undefined values, and Symbol keys from objects, and converts NaN and Infinity to null. BigInt values throw a TypeError outright. If a field is missing from your output, it is almost always one of these unsupported types.
Should I use 2 or 4 spaces to format JSON?
Match your ecosystem. JavaScript and Node default to 2 spaces (Prettier, npm, tsconfig.json), while Python tooling commonly uses 4. If your project runs a formatter on commit, use whatever it enforces so you are not fighting the auto-formatter. When unsure, 2 spaces is the safest default.



