Pretty Printing JSON in Python and From the Terminal
Pretty print JSON with json.dumps(indent), python -m json.tool and pprint, keep non-ASCII readable, sort keys and collapse short arrays.

Pretty printing is the most common thing anyone does with JSON that is not parsing it: turn the single-line blob into something a person can read. Python has three ways to do it, the shell has one more, and each makes slightly different choices about spacing, key order, non-ASCII characters and what happens with invalid input. This article covers all four with their output on the same small document, a trick for making arrays readable, and the cases where pretty printing the data is the wrong step.
json.dumps with indent
import json
print(json.dumps(d, indent=2)){
"id": 7,
"name": "Zoë",
"tags": [
"a",
"b"
],
"nested": {
"ok": true,
"none": null,
"n": 1.5
},
"nums": [
0,
1,
...Notice what the defaults decided. Keys are in insertion order. The ë was written as ë, because ensure_ascii=True escapes everything outside ASCII, which is correct and not very readable. And every array element is on its own line: the 12-number array takes 14 lines. That is the standard library's choice and it makes arrays of scalars tall.
The knobs you will want:
json.dumps(d, indent=2, ensure_ascii=False, sort_keys=True)ensure_ascii=False writes "name": "Zoë" as the character, valid in UTF-8 output; make sure whatever receives the string handles it. sort_keys=True gives alphabetical keys, handy for diffing two documents. indent accepts a string too: indent="\t" produced tab indentation. Sizes: pretty printing costs space, and the 36.6 MB compact-ish text of 200,000 records became 54.4 MB at indent=2; the default separators ", " and ": " are already wider than the minimal (",", ":"), which gave 32.4 MB. Pretty print for people, not for storage or transfer.
Making arrays readable
If tall arrays bother you (and long lists of numbers or short strings do), post-process the text to collapse arrays that contain only scalars when they fit on a line. A small function does it:
import json, re
def pretty(obj, indent=2, width=72):
text = json.dumps(obj, indent=indent, ensure_ascii=False)
def collapse(m):
inner = re.sub(r"\s*\n\s*", " ", m.group(0)).replace("[ ", "[").replace(" ]", "]")
return inner if len(inner) <= width else m.group(0)
return re.sub(r"\[[^\[\]{}]*\]", collapse, text)The pattern matches an array with no nested brackets or braces, so only arrays of scalars qualify, and the replacement keeps the multi-line form when the collapsed array is longer than width. On the sample document "tags": ["a", "b"] and "nums": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] each took one line and everything else was unchanged. Caveat: the regex also sees brackets inside string values, so a string containing [ and a newline could be mangled in theory; json.dumps escapes newlines inside strings as \n, so the multi-line pattern cannot start inside one, but a string such as "[ x ]" would be touched. For display only, that is acceptable; do not use the collapsed text as data without re-parsing it to verify it round-trips.
python -m json.tool
The standard library installs a formatter that works on files and pipes:
python -m json.tool data.json
echo '{"b":[1,2],"a":"x"}' | python -m json.tool --sort-keys
python -m json.tool in.json out.jsonPython 3.13's options, each tested on the sample:
--indent N N-space indent (--indent 1 gave one space)
--tab tab indent
--no-indent one line, default spacing, e.g. {"b": [1, 2, {"é": null}], "a": "x"}
--compact {"b":[1,2,{"é":null}],"a":"x"}
--sort-keys a before b at every level
--no-ensure-ascii é written as the character
--json-lines treat each input line as a separate documentWith no options it indents by four spaces. The tool reads the whole input and parses it, so it is also a validator: given not json it printed Expecting value: line 1 column 1 (char 0) and the exit status was 1; given {"a":1,} it printed Illegal trailing comma before end of object: line 1 column 7 (char 6), and given two documents glued together, Extra data: line 1 column 8 (char 7). Use it in a shell script as python -m json.tool file.json > /dev/null || exit 1. The --json-lines option makes it useful for NDJSON logs: two lines {"a":1} and {"a":2} came out as two separate pretty objects.
Writing to a second file argument works (python -m json.tool in.json out.json), and writing back to the same file via a pipe truncates it before it is read: python -m json.tool f.json > f.json leaves an empty file. Use the two-argument form, or a temporary file.
pprint is not JSON
pprint.pprint(d, width=60) is the other pretty printer in the standard library, and it prints a Python repr:
{'id': 7,
'name': 'Zoë',
'nested': {'n': 1.5, 'none': None, 'ok': True},
'nums': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
'tags': ['a', 'b']}Single quotes, None, True, and keys sorted. It wraps at a width instead of one item per line, which is compact and often nicer to read, and it is not JSON: no parser will accept it (JSON parse error with single quotes shows the failures when it ends up in a payload). Use pprint for looking at Python objects in a debugger or REPL and json.dumps(indent=...) for anything that must stay JSON.
The same in jq
jq is the other tool in most terminals. echo '{"b":[1,2,{"é":null}],"a":"x"}' | jq . printed the document with a two-space indent, and, unlike json.tool, left é as a character. jq -S -c . gave sorted compact output, jq --indent 1 . a one-space indent, and jq --tab . tabs. On invalid input jq 1.6 reported parse error: Unfinished JSON term at EOF at line 2, column 0. Use whichever is installed; python -m json.tool is present wherever Python is, and jq is better for the next step, filtering (using jq to slice up JSON in the terminal). The browser version is the JSON formatter, which also marks the position of an error.
Pretty printing large files
json.tool parses the whole input, so its memory use follows the file: it is a poor choice for a multi-gigabyte document. For NDJSON, --json-lines processes one line at a time. For a big single array, jq also loads the whole value (unless --stream is used), and ijson can feed a streaming writer (handling large JSON files in Python with ijson). Often the right move is not to format the whole file, but to look at the first record: head -c 2000 file.json, or jq '.[0]' file.json.
When pretty printing is the wrong tool
The output is data, not display. Indentation adds 49 percent to the size of the test file and changes nothing for a parser. Store and send compact JSON.
You diff or hash the text. Pretty printing is stable only with sort_keys=True and fixed settings, and the stability rules are in sorting keys and getting stable JSON output.
You are logging. One object per line is the log format; multi-line JSON breaks line-based tools. Pretty print on the way to the screen, not into the log file.
The reader is a person looking for a value. A query is better than a screenful: jq '.items[] | select(.sku=="A1")' or a path lookup in code.
The data contains secrets. Pretty printing to a terminal or a ticket spreads them; mask first.
The options are documented in the Python json module reference, including the command-line interface, and in the pprint documentation for the repr-style printer.


