Skip to content
Back to Blog
By JSONConvert Team··6 min read

How to Pretty Print JSON (jq, Python, Node)

You curl an endpoint and get one 40 KB line back. Pretty printing JSON is the step between "the request worked" and "I can read what came back", and there are three tools already on your machine that do it. They do not produce the same output. jq prints numbers exactly as they arrived, Python escapes every non-ASCII character unless you tell it not to, and Node rounds any integer above 2^53 before it prints. Pick the one whose defaults match what you are trying to see.

jq: the default for the terminal

Two-space indent, keys in the order they arrived, colors on when stdout is a terminal and off when piped. The flags worth memorizing:

jq is the only one of the three that prints numbers as they came in. Given {"id": 9007199254740993, "price": 19.90}, jq 1.7.1 prints 9007199254740993 and 19.90 back out, untouched. That matters for 64-bit IDs and for money fields where the trailing zero was deliberate. jq 1.6 and older did not do this. They converted every number through a double and would have printed 9007199254740992, so if the digits look wrong, check jq --version before you blame the API.

Invalid input exits with code 5 and tells you where it stopped:

Python: already installed, escapes Unicode

Four spaces by default, and one behavior that surprises people the first time: every non-ASCII character is escaped. "name": "café" comes out as "name": "caf\u00e9". That is valid JSON and parses back to the same string, but it is unreadable if you were pretty printing in order to read it, which is the whole point. Turn it off:

Python also normalizes floats. 19.90 becomes 19.9 because the value goes through a Python float and is printed with the shortest representation that round-trips. Integers keep every digit, so 9007199254740993 survives. A trailing comma or a single-quoted string fails with exit code 1 and a message like Illegal trailing comma before end of object: line 1 column 7 (char 6), which makes python3 -m json.tool file.json > /dev/null a serviceable validation step in CI when jq is not installed.

Inside a script the same knobs are keyword arguments:

Node: JSON.stringify with a third argument

JSON.stringify(value, null, 2) is the whole API. The third argument is the indent: a number of spaces up to 10, or a string such as "\t". In application code it is what every res.json and every config file write ends up calling.

The catch is that Node is JavaScript, so the parse step goes through Number. 9007199254740993 prints as 9007199254740992 and nothing warns you. If your payload carries snowflake IDs from Twitter, Discord, or a database BIGINT column, do not round-trip them through JSON.parse just to look at them. Use jq, or parse with a reviver that keeps them as strings. The large numbers guide walks through the reviver.

JSON.stringify also rewrites values that were never JSON to begin with. When you pretty print an in-memory object rather than a parsed string:
undefined and functions vanish, NaN becomes null, dates become ISO strings. Fine for logging. A trap if you are diffing that output against what another service sent.

The three side by side

Same input, {"id":9007199254740993,"name":"café","price":19.90}:

jq 1.7.1python3 -m json.toolNode 22
Default indent2 spaces4 spacesnone, you pass it
9007199254740993900719925474099390071992547409939007199254740992
19.9019.9019.919.9
"café""café""caf\u00e9""café"
Key orderpreservedpreservedpreserved
Sort keys-S--sort-keyswrite your own
Minify-c--compactomit the third argument
Bad input exit code511, uncaught exception
If the JSON is for a human to read, use jq. If jq is not installed and you cannot install it, use Python with --no-ensure-ascii. If you are already inside JavaScript, JSON.stringify is fine as long as nothing in the payload is above 2^53.

In the editor

VS Code formats a .json file with Shift+Alt+F on Windows and Linux, Shift+Option+F on macOS. The built-in JSON formatter only edits whitespace, so 19.90, 1E5, and a 19-digit ID all come out exactly as they went in. If Prettier is your default formatter, it takes over .json files too and it does normalize number literals: 19.90 becomes 19.9 and 1E5 becomes 1e5. It keeps integers intact, so IDs are safe, but a fixture file with "amount": 10.00 will get its zeros stripped on save.

In the browser

For a response you copied out of a network tab or a log line, paste it into the JSON formatter. It validates first, then reindents with your choice of width and optional sorted keys. The JSON beautifier does the same job with a simpler surface, and the JSON minifier goes the other way when you need a one-line payload for a curl command. For a deeply nested payload, the JSON viewer renders a collapsible tree, which is faster than scrolling through 400 lines of indentation looking for the one key you care about.

One honest caveat: these run on JSON.parse and JSON.stringify in your browser, so they inherit the Node column of the table above. A 64-bit ID will round. If you need every digit preserved, jq on the command line is the tool.

The error that means "this is not JSON"

The most common pretty printing failure is not malformed JSON. It is a request that returned HTML. An expired token, a redirect to a login page, a 404 from a proxy: all of them hand jq an angle bracket and you get this:

Python says Expecting value: line 1 column 1 (char 0). Neither message mentions HTML. When you see either one on the first character of a response, run the curl again with -i and read the status line before you touch the JSON.

Related Tools