JSON is everywhere. APIs return it, configs use it, logs contain it. jq is the Swiss Army knife for processing it all from the command line.

Basic Selection

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Pretty print
echo '{"name":"alice","age":30}' | jq .

# Extract a field
echo '{"name":"alice","age":30}' | jq '.name'
# Output: "alice"

# Raw output (no quotes)
echo '{"name":"alice","age":30}' | jq -r '.name'
# Output: alice

# Nested fields
echo '{"user":{"name":"alice"}}' | jq '.user.name'

Working with Arrays

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Get all elements
echo '[1,2,3]' | jq '.[]'
# Output:
# 1
# 2
# 3

# Get specific index
echo '["a","b","c"]' | jq '.[1]'
# Output: "b"

# Slice
echo '[1,2,3,4,5]' | jq '.[2:4]'
# Output: [3,4]

# First/last
echo '[1,2,3]' | jq 'first'   # 1
echo '[1,2,3]' | jq 'last'    # 3

Filtering

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Select objects matching condition
echo '[{"name":"alice","age":30},{"name":"bob","age":25}]' | \
  jq '.[] | select(.age > 27)'
# Output: {"name":"alice","age":30}

# Multiple conditions
jq '.[] | select(.status == "active" and .role == "admin")'

# Contains
jq '.[] | select(.tags | contains(["important"]))'

# Regex matching
jq '.[] | select(.email | test("@company\\.com$"))'

Transforming Data

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Create new object
echo '{"first":"Alice","last":"Smith"}' | \
  jq '{fullName: (.first + " " + .last)}'
# Output: {"fullName":"Alice Smith"}

# Map over array
echo '[1,2,3]' | jq 'map(. * 2)'
# Output: [2,4,6]

# Transform array of objects
echo '[{"name":"alice"},{"name":"bob"}]' | \
  jq 'map({user: .name, active: true})'

API Response Processing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Extract data from GitHub API
curl -s https://api.github.com/users/torvalds/repos | \
  jq '.[] | {name, stars: .stargazers_count, language}' | \
  jq -s 'sort_by(.stars) | reverse | .[0:5]'

# Get just names
curl -s https://api.github.com/users/torvalds/repos | \
  jq -r '.[].name'

# Count items
curl -s https://api.github.com/users/torvalds/repos | \
  jq 'length'

Aggregation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Sum
echo '[{"value":10},{"value":20},{"value":30}]' | \
  jq '[.[].value] | add'
# Output: 60

# Average
jq '[.[].value] | add / length'

# Group by
echo '[{"type":"a","n":1},{"type":"b","n":2},{"type":"a","n":3}]' | \
  jq 'group_by(.type) | map({type: .[0].type, total: [.[].n] | add})'

# Count by field
jq 'group_by(.status) | map({status: .[0].status, count: length})'

Building Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Concatenate to string
echo '{"host":"db","port":5432}' | \
  jq -r '"\(.host):\(.port)"'
# Output: db:5432

# Create CSV
echo '[{"name":"alice","age":30},{"name":"bob","age":25}]' | \
  jq -r '.[] | [.name, .age] | @csv'
# Output:
# "alice",30
# "bob",25

# Create TSV
jq -r '.[] | [.name, .age] | @tsv'

# URI encode
jq -r '@uri'

# Base64
jq -r '@base64'

Conditional Logic

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# If/then/else
echo '{"score":85}' | \
  jq 'if .score >= 90 then "A" elif .score >= 80 then "B" else "C" end'

# Alternative operator (default values)
echo '{"name":"alice"}' | jq '.age // 0'
# Output: 0

# Try (suppress errors)
echo '{"a":1}' | jq '.b.c.d // "missing"'
# Output: "missing"

Modifying JSON

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Update field
echo '{"name":"alice","age":30}' | jq '.age = 31'

# Add field
echo '{"name":"alice"}' | jq '. + {active: true}'

# Delete field
echo '{"name":"alice","temp":123}' | jq 'del(.temp)'

# Update nested
echo '{"user":{"name":"alice"}}' | jq '.user.name = "bob"'

# Recursive update
jq '.. | objects | .timestamp |= (. // now)'

Multiple Files

1
2
3
4
5
6
7
8
9
# Combine objects from files
jq -s '.[0] * .[1]' defaults.json overrides.json

# Process files independently
jq -r '.name' file1.json file2.json

# Slurp into array
jq -s '.' file1.json file2.json
# Output: [{...}, {...}]

Stream Processing

For large files, use streaming:

1
2
3
4
5
# Process line by line (JSONL)
cat events.jsonl | jq -c 'select(.level == "error")'

# Stream large arrays
jq --stream 'select(.[0][0] == "items")' huge.json

Practical Examples

Parse Docker Output

1
2
3
4
5
6
7
# Running containers with ports
docker ps --format '{{json .}}' | \
  jq -r 'select(.State == "running") | "\(.Names): \(.Ports)"'

# Container resource usage
docker stats --no-stream --format '{{json .}}' | \
  jq -s 'sort_by(.MemPerc | rtrimstr("%") | tonumber) | reverse'

Kubernetes Resources

1
2
3
4
5
6
7
# Pod status summary
kubectl get pods -o json | \
  jq -r '.items[] | "\(.metadata.name): \(.status.phase)"'

# Nodes with capacity
kubectl get nodes -o json | \
  jq '.items[] | {name: .metadata.name, cpu: .status.capacity.cpu, memory: .status.capacity.memory}'

AWS CLI Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# EC2 instances with tags
aws ec2 describe-instances | \
  jq '.Reservations[].Instances[] | {
    id: .InstanceId,
    type: .InstanceType,
    state: .State.Name,
    name: (.Tags // [] | map(select(.Key == "Name")) | .[0].Value // "unnamed")
  }'

# S3 buckets by size
aws s3api list-buckets | \
  jq -r '.Buckets[].Name'

Log Analysis

1
2
3
4
5
6
7
8
9
# Count errors by type
cat app.jsonl | \
  jq -s 'map(select(.level == "error")) | group_by(.error_type) | 
         map({type: .[0].error_type, count: length}) | 
         sort_by(.count) | reverse'

# Extract slow requests
cat access.jsonl | \
  jq 'select(.response_time_ms > 1000) | {path, response_time_ms}'

Useful Options

1
2
3
4
5
6
7
jq -r          # Raw output (no quotes)
jq -c          # Compact output (one line)
jq -s          # Slurp (read all into array)
jq -e          # Exit non-zero if output is null/false
jq --arg k v   # Pass string variable
jq --argjson k v  # Pass JSON variable
jq -f file.jq  # Read filter from file

The jq Cheatsheet

PatternPurpose
.fieldGet field
.[]Iterate array
.[0]First element
select(cond)Filter
map(expr)Transform each
{new: .old}Reshape
| addSum array
lengthCount
sort_by(.f)Sort
group_by(.f)Group
@csvTo CSV
.f // defaultWith default

Once jq clicks, you’ll wonder how you ever processed JSON without it.