Reading and inspecting JSON from the command line
Fetch, pretty-print and filter JSON with curl and jq, reshape it for a report, and know what a JSONC config file allows.
Fetch and format
# get it and look at it
curl -s https://api.example.com/orders | jq .
# a compact single line, useful for logs and diffs
curl -s https://api.example.com/orders | jq -c .
# the same with python, if jq is not installed
curl -s https://api.example.com/orders | python -m json.tool
# send a body and read the response
curl -s -X POST https://api.example.com/orders \
-H 'Content-Type: application/json' \
-d '{"sku":"A-1","quantity":2}' | jq .
# see status and body separately: -w writes to stdout after the body
curl -s -o /tmp/body.json -w '%{http_code}' https://api.example.com/orders
jq . /tmp/body.json-ssilences curl's progress meter, which otherwise corrupts the pipe.-Skeeps the error messages.jq .is the identity filter: it parses and re-emits, which both validates and pretty-prints.- A parse error from jq is your first signal that the response is HTML - a redirect to a login page is the classic case.
- Pipe through
jq -Sto sort keys, so two responses can be compared withdiff.
# what did the server actually send?
curl -sI https://api.example.com/orders | grep -i content-type
# and what does the error look like when the request is wrong?
curl -s https://api.example.com/orders/999 | jq .💡
Reach for
jq before reaching for a debugger. Most 'the API is broken' reports turn out to be a response shape that does not match the code's expectation, and jq . | head -40 shows that in one line.Selecting and reshaping
# navigate
jq '.data.orders[0].id'
jq '.data.orders | length'
jq '.data.orders[] | .id' # one per line
jq -r '.data.orders[] | .id' # raw strings, no quotes
# filter
jq '.data.orders[] | select(.status == "paid")'
jq '.data.orders[] | select(.total > 1000 and .currency == "GBP")'
jq '[.data.orders[] | select(.status != "cancelled")] | length'
# shape the output
jq '.data.orders[] | {id, total, currency}'
jq '{count: (.data.orders | length), total: ([.data.orders[].total] | add)}'
# transform keys and values
jq '.data.orders | map(.total = (.total / 100))'
jq '.data.orders | sort_by(.createdAt) | reverse | .[0:5]'
jq '.data.orders | group_by(.status) | map({status: .[0].status, n: length})'| Filter | Meaning |
|---|---|
.a.b | Navigate into an object |
.items[] | Iterate an array |
.[0:5] | Slice an array |
select(f) | Keep items where f is true |
map(f) | Apply f to every element |
{a, b} | Build a new object |
length, add, unique | Aggregate over an array |
to_entries, from_entries | Convert between object and pairs |
# exit status drives shell logic
if curl -s "$URL" | jq -e '.data.orders | length > 0' > /dev/null; then
echo "orders present"
else
echo "empty or unavailable"
fi
# export a table
jq -r '.data.orders[] | [.id, .status, (.total / 100)] | @csv' orders.json
# merge several files into one array
jq -s '.' page-1.json page-2.json page-3.json
# interpolate a shell value safely, without string concatenation
jq --arg id "$ORDER_ID" '.data.orders[] | select(.id == $id)' orders.jsonJSONC and JSON5 config files
Several tools accept a relaxed dialect for configuration. They are not JSON, and a parser that complies with the specification will reject them - which is exactly what happens when you copy a JSONC snippet into an API request body.
| Dialect | Allows | Used by |
|---|---|---|
| JSON | Nothing beyond the spec | APIs, data interchange |
| JSONC | Comments, trailing commas | VS Code settings, tsconfig |
| JSON5 | Comments, trailing commas, unquoted keys, single quotes, hex, Infinity | Some build configs |
| Relaxed JSON | Varies by parser | Anything that says so explicitly |
// a JSONC file: valid for the editor, invalid for JSON.parse
{
// the compiler options that matter for this package
"compilerOptions": {
"strict": true,
"noEmit": true,
},
}
// reading it in code needs a tolerant parser
import { parse } from "jsonc-parser";
const config = parse(await readFile("tsconfig.json", "utf8"));# strip comments and trailing commas before sending it anywhere
npx strip-json-comments tsconfig.json | jq . > /tmp/clean.json
# JSON5 from the command line
npx json5 -s config.json5# keep comments out of data files entirely
# config -> JSONC or YAML; payloads -> strict JSON, alwaysFAQ
Why does jq say 'Invalid numeric literal'?
The input is not JSON: it is probably HTML, a plain-text error page, or a response body that was already partially consumed. Check the content type and status code first, then look at the first few bytes with
head -c 200.Can I use comments in a JSON API body?
No. Comments are not part of the JSON grammar, and any compliant parser rejects them. If you want commented configuration, use JSONC, YAML or TOML for the file and convert to strict JSON before it is sent or stored as data.
Related
Syntax and types When to use something else
Last refreshed 2026-09-18.