jq Reference — Filters, Functions, and Recipes for JSON Processing
jq is the standard tool for parsing and transforming JSON on the command line. It’s everywhere in CI pipelines, shell scripts, and kubectl/AWS CLI workflows. The steep part isn’t the syntax — it’s knowing which built-in functions exist and how pipes work inside jq vs outside. This reference covers the patterns that come up in real scripts.
1. Basic Filters & Navigation
Field access, array indexing, pipes, and the identity filter
# Input JSON for examples:
# {"name":"Alice","age":30,"tags":["admin","dev"],"address":{"city":"London","zip":"EC1"}}
# Field access:
echo '...' | jq '.name' # "Alice"
echo '...' | jq '.address.city' # "London"
echo '...' | jq '.address["city"]' # same, bracket notation for keys with special chars
# Array access:
echo '...' | jq '.tags[0]' # "admin"
echo '...' | jq '.tags[-1]' # "dev" (last element)
echo '...' | jq '.tags[0:2]' # ["admin","dev"] (slice)
echo '...' | jq '.tags[]' # "admin"\n"dev" (iterator — one value per line)
# Pipe (output of left becomes input of right):
echo '...' | jq '.address | .city' # "London"
# Optional operator (? — no error if field missing):
echo '{"a":1}' | jq '.missing?' # null (not an error)
echo '{"a":1}' | jq '.missing // "default"' # "default" (alternative operator)
# -r flag: raw output (no quotes):
echo '...' | jq -r '.name' # Alice (without quotes)
# -c flag: compact output (no pretty-print):
echo '...' | jq -c '.address' # {"city":"London","zip":"EC1"}
# Multiple outputs:
echo '...' | jq '.name, .age' # "Alice"\n30
# Construct new object:
echo '...' | jq '{user: .name, loc: .address.city}'
# {"user":"Alice","loc":"London"}
2. Array Operations & Iteration
map, select, group_by, sort_by, unique_by, flatten, and array comprehension
# Input: [{"name":"Alice","age":30,"active":true},{"name":"Bob","age":25,"active":false},{"name":"Carol","age":35,"active":true}]
# map: transform each element:
jq 'map(.name)' # ["Alice","Bob","Carol"]
jq 'map({name: .name, senior: (.age > 30)})' # [{name,senior:false},{...},{name:Carol,senior:true}]
# select: filter elements (keep where condition is true):
jq 'map(select(.active))' # only active users
jq 'map(select(.age > 28))' # age > 28
jq '[.[] | select(.name | startswith("A"))]' # names starting with A
# sort_by:
jq 'sort_by(.age)' # ascending
jq 'sort_by(.age) | reverse' # descending
# group_by:
jq 'group_by(.active)' # [[{Bob}], [{Alice},{Carol}]]
# unique_by:
jq 'unique_by(.age)' # deduplicate by age
# flatten (nested arrays):
echo '[[1,2],[3,[4,5]]]' | jq 'flatten' # [1,2,3,4,5]
echo '[[1,2],[3,[4,5]]]' | jq 'flatten(1)' # [1,2,3,[4,5]] (depth 1 only)
# reduce (accumulate):
jq 'map(.age) | add' # 90 (sum of ages)
jq '[.[] | .age] | add / length' # average age
jq 'reduce .[] as $x (0; . + $x.age)' # explicit reduce
# any / all:
jq 'any(.[]; .active)' # true (any active?)
jq 'all(.[]; .age > 20)' # true (all over 20?)
3. String Operations & Formatting
String interpolation, split/join, test (regex), ltrimstr, and @base64/@csv/@tsv
# String interpolation (inside "\(...)"):
jq -r '"Hello, \(.name)! You are \(.age) years old."'
# Hello, Alice! You are 30 years old.
# split and join:
echo '"a,b,c"' | jq 'split(",")' # ["a","b","c"]
echo '["a","b","c"]' | jq 'join(",")' # "a,b,c"
# test: regex match (returns boolean):
echo '"hello-world"' | jq 'test("^hello")' # true
echo '"hello-world"' | jq 'test("[0-9]")' # false
# capture: named regex groups:
echo '"2026-03-14"' | jq 'capture("(?P[0-9]{4})-(?P[0-9]{2})-(?P[0-9]{2})")'
# {"year":"2026","month":"03","day":"14"}
# ltrimstr / rtrimstr:
echo '"https://example.com/path"' | jq 'ltrimstr("https://")' # "example.com/path"
# ascii_downcase / ascii_upcase:
echo '"Hello World"' | jq 'ascii_downcase' # "hello world"
# Format strings (@ encoders):
echo '"hello world"' | jq -r '@uri' # "hello%20world"
echo '"hello world"' | jq -r '@html' # "hello world" (& > < escaped)
echo '"aGVsbG8="' | jq -r '@base64d' # decoded base64
# Output as CSV/TSV:
jq -r '.[] | [.name, (.age|tostring)] | @csv' # "Alice",30
jq -r '.[] | [.name, (.age|tostring)] | @tsv' # Alice\t30
4. kubectl & AWS CLI Patterns
Real-world jq with kubectl output and AWS CLI JSON responses
# kubectl: list pod names in a namespace:
kubectl get pods -n production -o json | jq -r '.items[].metadata.name'
# kubectl: get image for each container in each pod:
kubectl get pods -o json | jq -r '.items[] | .metadata.name + ": " + .spec.containers[].image'
# kubectl: pods not Running:
kubectl get pods -o json | jq -r '.items[] | select(.status.phase != "Running") | .metadata.name'
# kubectl: node names + allocatable CPU:
kubectl get nodes -o json | jq -r '.items[] | .metadata.name + " " + .status.allocatable.cpu'
# AWS CLI: list EC2 instance IDs and private IPs:
aws ec2 describe-instances | \
jq -r '.Reservations[].Instances[] | .InstanceId + " " + .PrivateIpAddress'
# AWS CLI: only running instances:
aws ec2 describe-instances | \
jq -r '.Reservations[].Instances[] | select(.State.Name=="running") | .InstanceId'
# AWS CLI: get a specific tag value:
aws ec2 describe-instances | \
jq -r '.Reservations[].Instances[] |
.InstanceId + " " +
(.Tags // [] | map(select(.Key=="Name")) | first | .Value // "no-name")'
# GitHub API: list open PR titles:
gh api repos/{owner}/{repo}/pulls | jq -r '.[].title'
# Parse API response and check for errors:
RESPONSE=$(curl -s https://api.example.com/health)
echo "$RESPONSE" | jq -e '.status == "ok"' > /dev/null || { echo "API down"; exit 1; }
# -e: exit code 1 if result is false or null
5. Variables, Functions & --arg Injection
Pass shell variables into jq, define functions, and use env
# --arg: pass string from shell into jq:
NAME="Alice"
jq --arg name "$NAME" '.[] | select(.name == $name)'
# --argjson: pass JSON value (number, boolean, array):
jq --argjson min_age 25 '.[] | select(.age >= $min_age)'
# --args: pass positional args as $ARGS.positional array:
jq -n '$ARGS.positional' --args a b c # ["a","b","c"]
# env object (access environment variables):
jq -n 'env.HOME' # "/Users/alice"
jq -n 'env | keys' # list all env var names
# --rawfile: read a file as a raw string:
jq --rawfile template template.txt '{"body": $template}'
# Define reusable functions:
jq 'def to_mb: . / 1048576 | round;
def format_bytes: if . > 1048576 then (to_mb | tostring) + " MB" else (tostring) + " B" end;
.items[] | {name: .name, size: (.size | format_bytes)}'
# Recursive descent (..) — find all values of a key at any depth:
echo '{"a":{"b":{"id":1}},"c":{"id":2}}' | jq '[.. | .id? // empty]' # [1,2]
# debug: print intermediate values to stderr:
jq '.users | debug | map(.name)' # prints current value then continues
Track Linux toolchain releases at ReleaseRun. Related: Bash Reference | kubectl Reference | GitHub Actions Reference
Founded
2023 in London, UK
Contact
hello@releaserun.com