Formatting JSON: When It Helps, When It Hurts
A JSON beautifier (or pretty-printer) takes a compact JSON string and re-emits it with indentation and line breaks so a human can read it. That sounds trivial, but formatting is a tool with sharp edges: applied in the wrong place it inflates payloads and ruins log pipelines, and applied naively it can silently change your data. This guide treats formatting as an engineering decision — when to pretty-print, when to minify, and how to make the output deterministic enough to commit and diff.
A real scenario: debugging a 200 KB minified response
You call an API and the client logs a 200 KB response on a single line. Somewhere in there a price field is wrong, but the body looks like this:
{"order":{"id":"A-9912","items":[{"sku":"TS-01","qty":2,"price":19.99},{"sku":"MUG-7","qty":1,"price":-4.5}],"shipping":{"method":"ground","price":0},"total":35.48}}
You cannot scan that. Your eyes have nothing to anchor to — no structure, no nesting, no obvious boundary between objects. Paste it into a beautifier and you get:
{
"order": {
"id": "A-9912",
"items": [
{
"sku": "TS-01",
"qty": 2,
"price": 19.99
},
{
"sku": "MUG-7",
"qty": 1,
"price": -4.5
}
],
"shipping": {
"method": "ground",
"price": 0
},
"total": 35.48
}
}
Now the bug jumps out: MUG-7 has "price": -4.5. The indentation gives every value a vertical position, so you can trace order → items → [1] → price by eye in seconds. This is the core value of beautifying: it converts a string into a visible tree. Nothing about the data changed — only the whitespace — but the cognitive cost of reading it dropped by an order of magnitude.
The rule this illustrates: pretty-print is for humans reading data, not for machines moving it. The formatted version above is roughly twice the byte count of the minified one. That extra weight is exactly what you want at a debugging desk and exactly what you don't want on the wire.
The git-diff problem
Minified JSON fixtures are a common source of unreviewable pull requests. Suppose a test fixture is stored as one line and someone changes a single value. The diff looks like this:
-{"name":"widget","tags":["a","b"],"enabled":true,"weight":10}
+{"name":"widget","tags":["a","b"],"enabled":false,"weight":10}
Git highlights the entire line as changed because the line is the file. A reviewer has to character-diff the two strings to find that enabled flipped. Now store the same fixture pretty-printed with sorted keys:
{
- "enabled": true,
+ "enabled": false,
"name": "widget",
"tags": [
"a",
"b"
],
"weight": 10
}
The diff is now two lines and the change is unambiguous. Two things made this work: one value per line (from pretty-printing) and a stable key order (from sorting). Sorting matters because many serializers preserve insertion order, so two semantically identical objects built in different orders produce different files — and therefore noisy diffs. Sorting keys alphabetically removes that source of churn: the same logical data always serializes to the same bytes, so a diff shows only real changes. For any JSON you commit to a repo — fixtures, config, snapshot tests — pretty-print plus sorted keys is the default that keeps history reviewable.
When NOT to beautify
Formatting is not free, and there are places it actively hurts:
- Production API payloads. Every space and newline is a byte on the wire and a byte the client must parse. At scale, pretty-printing responses wastes bandwidth and latency for zero benefit, since no human reads a production response in flight. Send minified.
localStorage, cookies, and other size-bounded stores.localStoragecaps at roughly 5 MB per origin. Indentation is pure overhead against that budget. Store compact.- Log lines. Do not pretty-print JSON into logs. A multi-line JSON object breaks line-oriented tools (
grep,tail, log shippers) that assume one event per line. Use NDJSON instead — newline-delimited JSON, one compact object per line:
{"ts":"2026-06-25T10:00:01Z","level":"info","msg":"order placed","id":"A-9912"}
{"ts":"2026-06-25T10:00:02Z","level":"warn","msg":"negative price","sku":"MUG-7"}
Each line is independently parseable, greppable, and streamable. You pretty-print one line when you're inspecting it, not at write time.
The rule across all of these: pretty for humans, minified for transport and storage. Format at the point of reading, not at the point of writing.
Deterministic formatting choices
If formatted JSON is going into version control or a snapshot, the formatting must be reproducible. The knobs that matter:
- Indent width. 2 spaces is the de facto standard (npm, most JS tooling); 4 spaces is common in Python. Pick one per repo and never mix. Tabs work but render inconsistently across tools, so spaces are safer.
- Key ordering. Sort keys for anything that gets diffed. Leave insertion order alone only when the order is semantically meaningful (rare in JSON).
- Trailing newline. End the file with a single
\n. POSIX tools and many editors expect it, and its absence shows up as a spurious diff line. - Whitespace and gzip. The bandwidth argument against pretty-printing is weaker than people think once compression is on. Indentation is highly repetitive (runs of spaces, repeated
\n), which is exactly what gzip and brotli compress away. A pretty-printed response is often only a few percent larger than minified after gzip. So the real reasons to minify on the wire are reduced parse work and not shipping bytes you don't need — not a dramatic compressed-size difference. The reasons to pretty-print in a repo (readable diffs) usually win there.
The lossy gotcha: "format" is not always identity
It is tempting to think parse then stringify is a no-op that only changes whitespace. In JavaScript it is not. JSON.stringify silently drops or transforms values that have no JSON representation:
const obj = {
a: undefined, // dropped entirely
b: () => 1, // function: dropped
c: Symbol("x"), // symbol: dropped
d: NaN, // becomes null
e: Infinity, // becomes null
f: new Date(0), // becomes "1970-01-01T00:00:00.000Z"
g: 1n, // BigInt: THROWS TypeError
};
console.log(JSON.stringify(obj, null, 2));
// {
// "d": null,
// "e": null,
// "f": "1970-01-01T00:00:00.000Z"
// }
// (a, b, c are gone; remove `g` or it throws)
Three classes of surprise live here. Keys whose values are undefined, functions, or symbols vanish — so the formatted output has fewer keys than the input object. NaN and Infinity become null, which is a value change, not a formatting change. Date becomes an ISO string, so a round-trip turns a Date into a string you can no longer call .getTime() on. And BigInt throws outright. The lesson: "beautifying" a live JS object is a serialization, with all of serialization's lossiness — it is only an identity operation when you start from a JSON string and the values were already JSON-legal.
Code samples: format and sort keys
Pretty-print in JavaScript, and a replacer that sorts keys for stable output:
// Plain pretty-print, 2-space indent.
JSON.stringify(obj, null, 2);
// Pretty-print with keys sorted at every level (deterministic).
function sortedStringify(value, indent = 2) {
const seen = new Set();
const allKeys = [];
JSON.stringify(value, (k, v) => {
if (v && typeof v === "object" && !Array.isArray(v)) {
for (const key of Object.keys(v)) {
if (!seen.has(key)) { seen.add(key); allKeys.push(key); }
}
}
return v;
});
return JSON.stringify(value, allKeys.sort(), indent);
}
Note the replacer-array trick sorts keys globally; it works for most fixtures but be aware it applies one sorted key list to all objects. For strict per-object sorting, recursively rebuild the object with sorted keys before stringifying.
Python has sorting built in:
import json
print(json.dumps(obj, indent=2, sort_keys=True))
# Add ensure_ascii=False to keep UTF-8 characters readable.
Go formats with MarshalIndent; struct field order is fixed by declaration, and map keys are sorted automatically by the encoder:
b, err := json.MarshalIndent(obj, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
On the command line, jq is the fastest path. Bare jq . pretty-prints; -S sorts keys for deterministic output; -c minifies for transport:
# Pretty-print a file or piped response.
curl -s https://api.example.com/order | jq .
# Pretty-print with sorted keys (stable for committing).
jq -S . fixture.json > fixture.pretty.json
# Minify (compact) for storage or the wire.
jq -c . fixture.pretty.json > fixture.min.json
These four tools cover the practical cases: format for reading, sort for diffing, minify for shipping. Pick the indent width once, sort keys whenever the output is versioned, and remember that the formatter only preserves your data when the input was already valid JSON.
Related Tools & Resources
Tools
- JSON Validator & Formatter — Validate and pretty-print in one step so you catch syntax errors before formatting.
- JSON Formatter — Beautify minified JSON with configurable indentation, entirely in your browser.
- JSON Minify — Strip whitespace for production payloads, localStorage, and the wire.
- JSON Diff — Compare two JSON documents structurally instead of character-diffing raw strings.
- JSON to Table — Turn arrays of objects into a scannable grid when nesting is hard to read.
Learn More
- How to Open JSON Files — Practical ways to view and inspect JSON files across editors and tools.
- Mastering JSON in JavaScript — Goes deeper on stringify, parse, replacers, and the lossy cases shown above.
- Common JSON Mistakes — Avoid the syntax and serialization errors that break formatting.
- JSON Format Rules — The grammar behind valid JSON, so your beautifier never chokes on input.
Related Articles
Common JSON Mistakes and How to Avoid Them
The 20 most common JSON syntax errors developers make, organized by category with examples and fixes.
How to Fix 'Unexpected End of JSON Input' Error
Learn what causes the 'Unexpected end of JSON input' error and how to fix it with practical solutions for JavaScript, Python, and other languages.
How to Open JSON Files: A Practical Guide
Open, view, and troubleshoot JSON files on macOS, Windows, and Linux with real commands for jq, Python, VS Code, and browsers — plus fixes for common errors.
JSON Comments: Why They Don't Exist and How to Work Around It
Learn why JSON doesn't support comments, explore workarounds like JSONC and JSON5, and discover best practices for documenting your JSON files.