The actual problem
Every developer I know runs the same loop several times a day:
curl https://api.example.com/some-endpoint
The response comes back as one minified line of JSON. You squint, scroll, give up, and reach for a formatter.
I have ended up using three different formatters for this depending on the situation. None of these are novel — the interesting part is the picking. Below is the boring rule of thumb I actually follow, and the redaction step most posts about this skip entirely.
Approach 1: pipe through jq
This is the default and almost always the right answer:
curl -s https://api.example.com/users | jq .
- ✅ Fastest path to pretty output
- ✅ Tells you where the parse fails with a column reference
- ✅ Composable with filtering:
jq '.users[] | select(.active)' - ❌ Requires
jqto be installed (not guaranteed on minimal containers or fresh CI) - ❌ The filter mini-language has a learning curve
When to not reach for jq first: when you only need to read the response and you are already about to paste it somewhere else (a Slack thread, a GitHub issue, a ticket). At that point you are leaving the terminal anyway and the savings are marginal.
Approach 2: Python json.tool as a fallback
When jq is inconvenient to install — locked-down CI runners, remote SSH on a server you do not own, minimal Alpine images where you happen to have Python but not jq — the stdlib has you covered:
curl -s https://api.example.com/users | python3 -m json.tool
- ✅ Often available where installing
jqis inconvenient - ✅ Sufficient for pretty printing + parse validation
- ❌ Error messages are less precise than
jqon truncated input - ❌ No filter / select capability — it formats, that's it
I use this maybe twice a week. It is the "good enough" tool, not the "right" tool. Note that Python is not actually guaranteed either — distroless images and BusyBox containers ship with neither.
Approach 3: a browser-side formatter for the share step
I only paste curl output into a browser formatter when I am about to share the result with a teammate (Slack, GitHub issue, ticket review), and the payload is safe for that audience.
The reason that condition matters: a security firm disclosed last year that two popular online formatters had been retaining 80,000+ saved snippets, including AWS keys and JWTs. I covered the story separately in What the JSON Formatter Leaks Taught Me About Browser-Side Tools; here I only care about the practical rule it implies. Pick a static-site formatter that does no server-side conversion, verify in DevTools that no outgoing request carries your payload, and accept that this is something you re-verify per session and per build — not a permanent brand promise.
For my own sharing slot I use FormatArc's JSON formatter. It is a static site with no /api/* routes, the parser core is open source (repo), and the no-upload claim is checkable in your own DevTools before you paste anything real.
Redact before you share
This is the step most "format your curl response" posts leave out. Even when the payload looks innocent, it likely contains things that should not survive into a public Slack channel or a GitHub issue: API endpoints, internal IDs, error stack traces with file paths, customer references.
The pragmatic approach is to strip known secret-bearing keys with jq before you paste anywhere:
# Drop the obvious secrets at the top level
curl -s https://api.example.com/users \
| jq 'del(.token, .password, .authorization)'
# Walk the entire tree and drop them at every depth
curl -s https://api.example.com/users \
| jq 'walk(if type == "object"
then del(.password, .token, .secret, .authorization)
else . end)'
The second form catches secrets nested inside response bodies (data[].credentials.token, result.session.authorization, etc). I have it as a shell alias because I want zero friction between "I should redact this" and actually doing it.
A picking heuristic
| Situation | Use |
|---|---|
| Local terminal, exploring response shape | jq |
| Local terminal, need to filter or transform |
jq (or mlr if the data is tabular) |
CI / Docker without jq (but with Python) |
python3 -m json.tool |
| Pasting to share — already redacted, public-safe | browser formatter |
| Pasting to share — payload contains anything internal | redact in jq first |
| Large file (> 100 MB) |
jq --stream or DuckDB, not a browser |
For most of these rows, jq is the answer. The "switching between three" framing is mostly an excuse to write down the boundaries — that is where I see people lose time.
What I am explicitly not claiming
jq is not "my discovery." It has been the default in this niche for over a decade. python3 -m json.tool is well-known. The only thing I am asserting here is that the boundaries between these three approaches, plus the redaction step, are where the friction actually lives.
A few honest limits worth flagging:
- A browser-side formatter is not immune to leaks — the threat model still includes XSS in the parser, malicious extensions reading the DOM, supply-chain hits on npm deps. The browser is a smaller blast radius than a server-side formatter, not a zero one. Re-verify the no-upload claim in DevTools per session or build; do not trust it as branding.
-
python3 -m json.toolis the fallback. Ifjqis reachable, preferjq.
Try them
-
jq: https://stedolan.github.io/jq/ - FormatArc JSON formatter: https://formatarc.com/en/json-formatter/
For a deeper dive on why JSON specs disallow trailing commas or comments (with cross-parser verification against GitHub, marked, and remark-gfm), see Are JSON comments allowed? RFC 8259 and 4 fixes. It is the article Google's AI Overview cites when asked about ECMA-404 / RFC 8259 comment rules.
If you have a redaction recipe better than the walk(...) filter above — especially for nested envelopes from common API frameworks (Laravel, Rails, NestJS, Spring) — I would like to read it.
Top comments (0)