2 AM. A Telegram alert fires: service health checks failing repeatedly. SSH into the server, tail -f the logs — an endless wall of JSON crammed onto a single line. You need to know immediately what the status field says and which node in the cluster is down.
At that moment you have three options: grep to search raw text, fire up Python to parse JSON, or use jq. That was the night I truly understood why jq exists.
Comparing 3 Ways to Handle JSON in the Terminal
Same problem: an API returns a JSON response and you need to extract the status field:
curl -s https://api.example.com/health
# Output: {"id":1,"status":"error","message":"Connection timeout","node":"web-03"}
Option 1: grep and sed
curl -s https://api.example.com/health | grep -o '"status":"[^"]*"'
# "status":"error"
Pros: Available on every Linux system out of the box, no installation needed.
Cons: The result is still "status":"error" — not the bare value. If the server returns "status" : "error" with extra spaces, or changes the field order, the regex either matches incorrectly or returns nothing at all. At 2 AM in production, “hoping for the best” is not a good option.
Option 2: Python one-liner
curl -s https://api.example.com/health | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['status'])"
# error
Pros: Parses JSON correctly, not fooled by formatting or field ordering.
Cons: The command is long and painful to type when your brain hasn’t fully booted up yet. And when you need to access nested objects or arrays, it gets even more unwieldy.
Option 3: jq
curl -s https://api.example.com/health | jq '.status'
# "error"
One command. Short. Clear. Instant results.
When to Use Each Approach
- grep/sed: Only when you can’t install additional tools on the server and the JSON structure is extremely simple and will never change format.
- Python: When you need complex logic, multi-step conditional processing, or transforming data to write to a file — things jq can do but Python is easier to maintain in longer scripts.
- jq: Any time you need to filter, extract, or transform JSON quickly in the terminal or a short shell script. This is the default choice.
After three years managing over 10 VPS instances, I’ve seen this happen once firsthand: an API vendor changed their response format, grep silently stopped matching anything, the script kept running as if nothing was wrong — but the status I was reading was an empty string. jq doesn’t have that problem. It parses to spec, independent of formatting or field order.
Installing jq
# Ubuntu/Debian
sudo apt install jq
# CentOS/AlmaLinux/RHEL
sudo dnf install jq
# Arch Linux
sudo pacman -S jq
# Verify installation
jq --version
# jq-1.7.1
jq Basic Syntax
Pretty-print JSON for readability
The first step when debugging: dump the entire JSON with nice formatting:
curl -s https://api.example.com/health | jq '.'
The dot . means “take the entire input and reformat it.” Instead of one unreadable line, you see:
{
"id": 1,
"status": "error",
"message": "Connection timeout",
"node": "web-03"
}
Extracting specific fields
# Get a single field
echo '{"status":"error","node":"web-03"}' | jq '.status'
# "error"
# Get multiple fields at once
jq '.status, .node'
# Nested field
jq '.server.config.port'
Working with arrays
cat nodes.json
# [{"name":"web-01","status":"ok"},{"name":"web-02","status":"ok"},{"name":"web-03","status":"error"}]
# All elements
cat nodes.json | jq '.[]'
# First element (index 0)
cat nodes.json | jq '.[0]'
# Get the name field from each element
cat nodes.json | jq '.[].name'
# "web-01"
# "web-02"
# "web-03"
Filtering with select() — your most-used tool when debugging
Pinpoint exactly which node is failing instead of reading through the entire list:
cat nodes.json | jq '.[] | select(.status == "error")'
# {
# "name": "web-03",
# "status": "error"
# }
# Get only the name of the failing node
cat nodes.json | jq -r '.[] | select(.status == "error") | .name'
# web-03
Real-World Usage with Docker and Kubernetes
jq isn’t just for external APIs. Docker and Kubernetes both output JSON — and this is where jq really shines:
# List name and IP of all containers in the bridge network
docker network inspect bridge | jq '.[0].Containers | to_entries[] | {name: .value.Name, ip: .value.IPv4Address}'
# View port mappings for the nginx container
docker inspect nginx | jq '.[0].NetworkSettings.Ports'
# Get database environment variables from the app container
docker inspect myapp | jq -r '.[0].Config.Env[]' | grep DB_
# Find Kubernetes pods not in Running state
kubectl get pods -o json | jq -r '.items[] | select(.status.phase != "Running") | .metadata.name'
Transforming data — building new objects from a response
# Build a concise report from a list of nodes
curl -s https://api.example.com/nodes | jq '[.[] | {node: .name, healthy: (.status == "ok")}]'
# [
# {"node": "web-01", "healthy": true},
# {"node": "web-03", "healthy": false}
# ]
# Count the number of failing nodes
cat nodes.json | jq '[.[] | select(.status == "error")] | length'
# 1
Using jq output in shell variables
# By default jq wraps strings in double quotes
echo '{"node":"web-03"}' | jq '.node'
# "web-03"
# The -r flag strips the quotes — use directly in a variable
NODE=$(curl -s https://api.example.com/health | jq -r '.node')
ssh admin@$NODE 'sudo systemctl restart myapp'
Viewing JSON When You’re Not on the Server
Sometimes you need to inspect a JSON response structure before writing a jq filter — but you’re on your local machine, not yet SSH’d into the server. In those situations I often reach for ToolCraft’s JSON Formatter: paste your JSON in, it formats instantly with clear syntax highlighting and catches syntax errors on the spot. The tool runs entirely in the browser with no data sent anywhere — which matters when API responses contain tokens or other sensitive information.
If you work heavily with Kubernetes or Ansible, the YAML ↔ JSON Converter on the same site is also handy — convert a YAML manifest to JSON to write and test jq filters, then convert back when you need to.
jq Quick Reference Cheat Sheet
jq '.' # Pretty-print
jq '.field' # Get a field
jq '.a.b.c' # Nested field
jq '.[]' # All array elements
jq '.[0]' # First element (index 0)
jq '.[] | select(.x == "val")' # Filter by string value
jq '.[] | select(.n > 10)' # Filter by number
jq '{new_key: .old_key}' # Create a new object
jq '[.[] | .name]' # Build an array from a field
jq 'length' # Count elements
jq 'keys' # List object keys
jq 'unique' # Unique values
jq 'sort_by(.field)' # Sort by field
jq -r '.field' # Raw output (no quotes)
jq -c '.' # Compact output (single line)
Ever since that 2 AM debugging session, jq has been the first thing I install on every new server. Add sudo apt install jq to your setup runbook and be done with it — the next time you hit a wall of JSON in your logs, you won’t have to paste it into a browser just to read it.

