If you work with APIs, logs, or configuration files, you work with JSON. And if you work with JSON from the command line, jq is indispensable. Here are the patterns I use daily.
The Basics#
1
2
3
4
5
6
7
8
9
10
| # Pretty print
echo '{"name":"alice","age":30}' | jq '.'
# Extract a field
echo '{"name":"alice","age":30}' | jq '.name'
# "alice"
# Raw output (no quotes)
echo '{"name":"alice","age":30}' | jq -r '.name'
# alice
|
The -r flag is your friend. Use it whenever you want the actual value, not a JSON string.
Working with Arrays#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # Get all elements
echo '[1,2,3]' | jq '.[]'
# 1
# 2
# 3
# Get specific index
echo '["a","b","c"]' | jq '.[1]'
# "b"
# Get length
echo '[1,2,3,4,5]' | jq 'length'
# 5
# First and last
echo '[1,2,3,4,5]' | jq 'first' # 1
echo '[1,2,3,4,5]' | jq 'last' # 5
# Slice
echo '[0,1,2,3,4,5]' | jq '.[2:4]'
# [2, 3]
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| DATA='{"user":{"profile":{"email":"alice@example.com"}}}'
# Chain keys
echo $DATA | jq '.user.profile.email'
# "alice@example.com"
# Optional access (no error if missing)
echo '{}' | jq '.user.profile.email?'
# null
# With default
echo '{}' | jq '.user.profile.email // "no email"'
# "no email"
|
Filtering Arrays#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| USERS='[{"name":"alice","age":30},{"name":"bob","age":25},{"name":"carol","age":35}]'
# Select by condition
echo $USERS | jq '.[] | select(.age > 28)'
# {"name":"alice","age":30}
# {"name":"carol","age":35}
# Extract field from filtered results
echo $USERS | jq '[.[] | select(.age > 28) | .name]'
# ["alice", "carol"]
# Multiple conditions
echo $USERS | jq '.[] | select(.age > 25 and .name != "carol")'
# {"name":"alice","age":30}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # Build new objects
echo '{"first":"Alice","last":"Smith"}' | jq '{fullName: "\(.first) \(.last)"}'
# {"fullName": "Alice Smith"}
# Map over arrays
echo '[1,2,3]' | jq 'map(. * 2)'
# [2, 4, 6]
# Add fields
echo '{"name":"alice"}' | jq '. + {role: "admin"}'
# {"name":"alice","role":"admin"}
# Update fields
echo '{"name":"alice","age":30}' | jq '.age = .age + 1'
# {"name":"alice","age":31}
# Delete fields
echo '{"name":"alice","age":30,"temp":true}' | jq 'del(.temp)'
# {"name":"alice","age":30}
|
String Manipulation#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # Interpolation
echo '{"name":"alice"}' | jq '"Hello, \(.name)!"'
# "Hello, alice!"
# Split and join
echo '"a,b,c"' | jq 'split(",")'
# ["a", "b", "c"]
echo '["a","b","c"]' | jq 'join("-")'
# "a-b-c"
# Case conversion
echo '"Hello World"' | jq 'ascii_downcase'
# "hello world"
# Test with regex
echo '"user@example.com"' | jq 'test("@")'
# true
|
Aggregation#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| NUMBERS='[1,2,3,4,5]'
echo $NUMBERS | jq 'add' # 15
echo $NUMBERS | jq 'min' # 1
echo $NUMBERS | jq 'max' # 5
echo $NUMBERS | jq 'add/length' # 3 (average)
# Group by
ITEMS='[{"type":"fruit","name":"apple"},{"type":"veg","name":"carrot"},{"type":"fruit","name":"banana"}]'
echo $ITEMS | jq 'group_by(.type)'
# [[{"type":"fruit","name":"apple"},{"type":"fruit","name":"banana"}],[{"type":"veg","name":"carrot"}]]
# Count by type
echo $ITEMS | jq 'group_by(.type) | map({type: .[0].type, count: length})'
# [{"type":"fruit","count":2},{"type":"veg","count":1}]
|
Real-World API Examples#
Parse AWS CLI Output#
1
2
3
4
5
6
7
8
9
| # List EC2 instance IDs and states
aws ec2 describe-instances | jq -r '.Reservations[].Instances[] | "\(.InstanceId)\t\(.State.Name)"'
# Get only running instances
aws ec2 describe-instances | jq '.Reservations[].Instances[] | select(.State.Name == "running")'
# Extract specific tags
aws ec2 describe-instances | jq -r '.Reservations[].Instances[] |
"\(.InstanceId)\t\(.Tags // [] | map(select(.Key == "Name")) | .[0].Value // "unnamed")"'
|
Parse Docker Output#
1
2
3
4
5
| # Container names and status
docker inspect $(docker ps -q) | jq -r '.[] | "\(.Name)\t\(.State.Status)"'
# Get container IPs
docker inspect $(docker ps -q) | jq -r '.[] | "\(.Name): \(.NetworkSettings.IPAddress)"'
|
Parse Kubernetes Output#
1
2
3
4
5
6
7
8
9
10
11
12
| # Pod names and status
kubectl get pods -o json | jq -r '.items[] | "\(.metadata.name)\t\(.status.phase)"'
# Pods not running
kubectl get pods -o json | jq '.items[] | select(.status.phase != "Running") | .metadata.name'
# Resource requests
kubectl get pods -o json | jq '.items[] | {
name: .metadata.name,
cpu: .spec.containers[0].resources.requests.cpu,
memory: .spec.containers[0].resources.requests.memory
}'
|
Parse GitHub API#
1
2
3
4
5
6
7
8
9
10
| # List repo names
curl -s "https://api.github.com/users/octocat/repos" | jq -r '.[].name'
# Stars and forks
curl -s "https://api.github.com/users/octocat/repos" | \
jq -r '.[] | "\(.name): ⭐\(.stargazers_count) 🍴\(.forks_count)"'
# Most starred
curl -s "https://api.github.com/users/octocat/repos" | \
jq -r 'sort_by(.stargazers_count) | reverse | .[0:5] | .[].name'
|
Multiple Outputs to Single Array#
1
2
3
4
5
6
7
| # Wrap multiple outputs in an array
echo '{"a":1,"b":2}' | jq '[.a, .b]'
# [1, 2]
# Collect filtered results
echo '[1,2,3,4,5]' | jq '[.[] | select(. > 2)]'
# [3, 4, 5]
|
Reading from Files#
1
2
3
4
5
6
7
8
| # From file
jq '.config.database' config.json
# Multiple files
jq -s '.' file1.json file2.json # Slurp into array
# Combine objects from multiple files
jq -s 'add' defaults.json overrides.json
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # Compact (no whitespace)
echo '{"a": 1, "b": 2}' | jq -c '.'
# {"a":1,"b":2}
# Tab-separated (for further processing)
echo '[{"a":1,"b":2},{"a":3,"b":4}]' | jq -r '.[] | [.a, .b] | @tsv'
# 1 2
# 3 4
# CSV
echo '[{"a":1,"b":2},{"a":3,"b":4}]' | jq -r '.[] | [.a, .b] | @csv'
# 1,2
# 3,4
# URI encoding
echo '"hello world"' | jq -r '@uri'
# hello%20world
|
Error Handling#
1
2
3
4
5
6
7
8
9
10
11
| # Suppress errors
echo 'not json' | jq '.' 2>/dev/null || echo "Invalid JSON"
# Check if valid JSON
if echo "$DATA" | jq -e '.' >/dev/null 2>&1; then
echo "Valid JSON"
fi
# The -e flag sets exit code based on output (null/false = 1)
echo 'null' | jq -e '.' # exits 1
echo '1' | jq -e '.' # exits 0
|
My .bashrc Helpers#
1
2
3
4
5
6
7
8
9
10
11
| # Pretty print JSON from clipboard
alias jqc='pbpaste | jq .'
# Format JSON file in place
jqf() { jq '.' "$1" > "$1.tmp" && mv "$1.tmp" "$1"; }
# Extract field from JSON stream (logs, etc.)
jqfield() { jq -r ".$1 // empty"; }
# Quick API response inspection
apiq() { curl -s "$1" | jq '.'; }
|
Quick Reference#
| Task | Command |
|---|
| Pretty print | jq '.' |
| Extract field | jq '.fieldname' |
| Raw output | jq -r '.field' |
| Array elements | jq '.[]' |
| Filter array | jq '.[] | select(.x > 5)' |
| Map array | jq 'map(.field)' |
| Build object | jq '{new: .old}' |
| Count | jq 'length' |
| Sort | jq 'sort_by(.field)' |
| Unique | jq 'unique' |
| First N | jq '.[0:5]' |
jq has a learning curve, but it’s worth climbing. Once fluent, you’ll parse JSON faster than opening a browser to find an online formatter. The command line becomes your data transformation playground.
For deeper exploration, the official manual is comprehensive, and jqplay.org lets you experiment interactively.
📬 Get the Newsletter
Weekly insights on DevOps, automation, and CLI mastery. No spam, unsubscribe anytime.