APIs return JSON. Config files use JSON. Logs are often JSON. The jq tool lets you slice, filter, and transform JSON from the command line—no scripting required.

Basic Syntax

1
2
3
4
5
jq 'filter' input.json
# or
cat input.json | jq 'filter'
# or
curl -s api.example.com | jq 'filter'

Pretty Print

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Ugly JSON
echo '{"name":"Alice","age":30}' | jq
# Output:
# {
#   "name": "Alice",
#   "age": 30
# }

# Compact (remove whitespace)
echo '{"name": "Alice"}' | jq -c
# {"name":"Alice"}

Extract Fields

1
2
3
4
5
6
echo '{"name":"Alice","age":30}' | jq '.name'
# "Alice"

# Without quotes
echo '{"name":"Alice","age":30}' | jq -r '.name'
# Alice

Nested Objects

1
2
echo '{"user":{"name":"Alice","email":"alice@example.com"}}' | jq '.user.name'
# "Alice"

Arrays

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Get element by index
echo '[1,2,3,4,5]' | jq '.[0]'
# 1

# Get slice
echo '[1,2,3,4,5]' | jq '.[1:3]'
# [2, 3]

# Get all elements
echo '[{"name":"Alice"},{"name":"Bob"}]' | jq '.[].name'
# "Alice"
# "Bob"

# Wrap in array
echo '[{"name":"Alice"},{"name":"Bob"}]' | jq '[.[].name]'
# ["Alice", "Bob"]

Filtering

1
2
3
4
5
6
# Select objects matching condition
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | jq '.[] | select(.age > 26)'
# {"name": "Alice", "age": 30}

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

Transform Data

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

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

# Add field
echo '{"name":"Alice"}' | jq '. + {role: "admin"}'
# {"name": "Alice", "role": "admin"}

Real-World Examples

Parse API response:

1
2
3
4
5
6
curl -s https://api.github.com/users/torvalds | jq '{
  name: .name,
  company: .company,
  repos: .public_repos,
  followers: .followers
}'

Get all container names:

1
docker inspect $(docker ps -q) | jq -r '.[].Name'

Extract from AWS CLI:

1
2
3
4
aws ec2 describe-instances | jq -r '
  .Reservations[].Instances[] | 
  "\(.InstanceId)\t\(.State.Name)\t\(.PrivateIpAddress)"
'

Filter logs:

1
cat app.log | jq -c 'select(.level == "error")'

Sum values:

1
2
echo '[{"price":10},{"price":20},{"price":30}]' | jq '[.[].price] | add'
# 60

Group by field:

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

Conditionals

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# If-then-else
echo '{"status":"active"}' | jq 'if .status == "active" then "✓" else "✗" end'
# "✓"

# Default values
echo '{"name":"Alice"}' | jq '.age // 0'
# 0

# Alternative operator
echo 'null' | jq '. // "default"'
# "default"

String Operations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Split
echo '"a,b,c"' | jq 'split(",")'
# ["a", "b", "c"]

# Join
echo '["a","b","c"]' | jq 'join("-")'
# "a-b-c"

# Length
echo '"hello"' | jq 'length'
# 5

# Contains
echo '"hello world"' | jq 'contains("world")'
# true

# Regex test
echo '"user@example.com"' | jq 'test("@")'
# true

Keys and Values

1
2
3
4
5
6
7
8
echo '{"a":1,"b":2}' | jq 'keys'
# ["a", "b"]

echo '{"a":1,"b":2}' | jq 'values'
# [1, 2]

echo '{"a":1,"b":2}' | jq 'to_entries'
# [{"key":"a","value":1},{"key":"b","value":2}]

Sorting

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Sort array
echo '[3,1,2]' | jq 'sort'
# [1, 2, 3]

# Sort objects by field
echo '[{"name":"Bob"},{"name":"Alice"}]' | jq 'sort_by(.name)'
# [{"name":"Alice"},{"name":"Bob"}]

# Reverse
echo '[1,2,3]' | jq 'reverse'
# [3, 2, 1]

Unique

1
2
3
4
5
echo '[1,2,2,3,3,3]' | jq 'unique'
# [1, 2, 3]

echo '[{"a":1},{"a":1},{"a":2}]' | jq 'unique_by(.a)'
# [{"a":1},{"a":2}]

Multiple Outputs

1
2
3
4
5
6
7
8
# Output multiple values
echo '{"a":1,"b":2}' | jq '.a, .b'
# 1
# 2

# Combine into array
echo '{"a":1,"b":2}' | jq '[.a, .b]'
# [1, 2]

Reading from Files

1
2
3
4
5
6
# Slurp multiple JSON objects into array
jq -s '.' file1.json file2.json

# Process each line as JSON
jq -c '.' <<< '{"a":1}
{"b":2}'

Raw Input/Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Raw string output (no quotes)
echo '{"name":"Alice"}' | jq -r '.name'
# Alice

# Raw input (treat as string, not JSON)
echo 'plain text' | jq -R '.'
# "plain text"

# Null input (for generating JSON)
jq -n '{name: "Alice", age: 30}'
# {"name": "Alice", "age": 30}

Combining with Shell

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Use in shell variable
NAME=$(echo '{"name":"Alice"}' | jq -r '.name')
echo "Hello, $NAME"

# Loop over array
echo '[1,2,3]' | jq -r '.[]' | while read num; do
    echo "Number: $num"
done

# Use shell variable in jq
NAME="Alice"
jq -n --arg name "$NAME" '{name: $name}'
# {"name": "Alice"}

Error Handling

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Suppress errors
echo 'invalid json' | jq '.' 2>/dev/null

# Check if valid JSON
if echo "$DATA" | jq -e '.' >/dev/null 2>&1; then
    echo "Valid JSON"
fi

# Optional object access
echo '{}' | jq '.missing.nested?'
# null (no error)

Cheat Sheet

TaskCommand
Pretty printjq '.'
Get fieldjq '.field'
Get nestedjq '.a.b.c'
Array elementjq '.[0]'
All elementsjq '.[]'
Filterjq '.[] | select(.x > 5)'
Mapjq 'map(.x * 2)'
Keysjq 'keys'
Lengthjq 'length'
Sortjq 'sort_by(.field)'
Uniquejq 'unique'
Raw outputjq -r '.field'
Compactjq -c '.'

jq turns JSON wrangling from a programming task into a one-liner. Learn the basics and you’ll use it daily.