Skip to content

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