skip to content

jq β€” JSON Processor

Slice, filter, map, and transform JSON data from the command line. Covers all essential filters, built-in functions, select, map, reduce, streaming, and real-world API response processing.

6 min read 15 snippets 2d ago deep dive

jq β€” JSON Processor#

Installation#

sudo apt install jq
sudo dnf install jq
brew install jq

Syntax#

jq [OPTIONS] FILTER [FILE...]
cat data.json | jq FILTER
curl -s https://api.example.com | jq '.'

Essential options#

OptionMeaning
-rRaw output (unquoted strings)
-cCompact output (no pretty print)
-nNull input (create JSON without stdin)
-eExit 1 if last output is false/null
-sSlurp: read all inputs into an array
-RRaw input: treat input as raw strings
-jJoin output (like -r but no trailing newline)
--arg NAME VALPass shell variable as string
--argjson NAME VALPass shell variable as JSON
--slurpfile NAME FILELoad file as JSON into variable
--tabUse tabs for indentation
--indent NUse N spaces

Basic filters#

jq '.'                       # pretty print (identity)
jq '.key'                    # get object field
jq '.key.nested'             # nested field
jq '.key?'                   # optional (no error if missing)
jq '.[0]'                    # array index (0-based)
jq '.[-1]'                   # last element
jq '.[2:5]'                  # array slice (indices 2,3,4)
jq '.[]'                     # iterate over array/object values
jq 'keys'                    # array of object keys
jq 'values'                  # array of object values
jq 'length'                  # length of array/string/object
jq 'type'                    # "null","boolean","number","string","array","object"

Comma and pipe#

jq '.name, .age'             # output multiple fields (separate lines)
jq '.items | length'         # pipe: get array length
jq '.items[] | .name'        # iterate items, get name field
jq '.data | .[] | select(.active)'  # chain

Object and array construction#

jq '{name: .name, age: .age}'           # build new object
jq '{(.key): .value}'                   # dynamic key from expression
jq '[.items[] | .id]'                   # build array from iteration
jq '[ .[] | select(.score > 50) ]'      # array of filtered elements
jq '{"total": (.items | length)}'       # computed field

select β€” filtering#

jq '.[] | select(.active == true)'
jq '.[] | select(.score > 80)'
jq '.[] | select(.name | startswith("A"))'
jq '.[] | select(.tags | contains(["linux"]))'
jq '.[] | select(.status == "error" or .status == "warn")'
jq '.[] | select(.value != null)'
jq '.[] | select(has("email"))'
jq '.[] | select(.count >= 10 and .count <= 100)'

map and map_values#

jq 'map(.price * 1.1)'                  # transform each element
jq 'map(select(.active))'               # filter array
jq 'map({id, name})'                    # project fields (shorthand)
jq 'map(.tags[])'                       # flatten one level
jq '[.[] | .name] | map(ascii_upcase)'  # uppercase all names
jq 'map_values(. + 1)'                  # increment every value in object

String operations#

jq '.name | ascii_upcase'
jq '.name | ascii_downcase'
jq '.name | ltrimstr("prefix")'
jq '.name | rtrimstr(".json")'
jq '.name | split("/")'                 # split string β†’ array
jq '.parts | join(", ")'                # join array β†’ string
jq '.s | test("^Error")'               # regex test β†’ boolean
jq '.s | match("([0-9]+)")'            # regex match object
jq '.s | gsub("foo"; "bar")'            # regex replace (jq 1.6+)
jq '"\(.name) is \(.age) years old"'   # string interpolation
jq '@base64'                            # base64 encode
jq '@base64d'                           # base64 decode
jq '@uri'                               # URL-encode
jq '@html'                             # HTML-escape
jq '@csv'                              # format as CSV row
jq '@tsv'                              # format as TSV row
jq '@json'                             # serialize to JSON string

Math and comparisons#

jq '.price | floor'
jq '.value | ceil'
jq '.x | sqrt'
jq '.a + .b'
jq 'if .score >= 90 then "A" elif .score >= 80 then "B" else "C" end'
jq '.items | sort_by(.name)'
jq '.items | sort_by(.price) | reverse'
jq '.items | unique_by(.category)'
jq '.items | group_by(.status)'
jq '.items | min_by(.price)'
jq '.items | max_by(.score)'
jq '[.[] | .price] | add'              # sum all prices
jq '[.[] | .price] | add / length'     # average price

Recursive and flatten#

jq '.. | .name? // empty'              # recurse all levels
jq '[.. | numbers]'                    # all numbers anywhere in tree
jq 'flatten'                           # flatten nested arrays completely
jq 'flatten(1)'                        # flatten one level
jq 'paths'                             # all paths in the document
jq 'paths(scalars)'                    # paths to scalar values
jq 'getpath(["a","b"])'               # value at path
jq 'setpath(["a","b"]; 99)'            # set value at path
jq 'delpaths([["a","b"]])'            # delete at path

reduce and foreach#

# Sum all numbers in array
jq 'reduce .[] as $x (0; . + $x)'

# Running totals
jq '[foreach .[] as $x (0; . + $x)]'

# Group and sum
jq 'group_by(.category) | map({key: .[0].category, value: map(.amount) | add}) | from_entries'

Input from shell variables#

jq --arg name "Alice" '.[] | select(.name == $name)'
jq --argjson min 50 '.[] | select(.score > $min)'
jq -n --arg ts "$(date -Iseconds)" '{"timestamp": $ts}'

# Pass entire JSON from shell
DATA='{"key":"val"}'
echo "$DATA" | jq --argjson extra '{"extra":true}' '. + $extra'

Null coalescing and defaults#

jq '.missing // "default"'             # use "default" if null/false
jq '.x // empty'                       # produce no output if null/false
jq '.[] | .name // "unknown"'          # fallback per element

Practical recipes#

# Pretty-print and paginate
curl -s https://api.example.com/users | jq '.' | less

# Extract a flat list of values (one per line)
jq -r '.[].name' users.json

# CSV export
jq -r '.[] | [.id, .name, .email] | @csv' users.json

# Convert array of objects to key=value lines
jq -r '.[] | "\(.key)=\(.value)"' config.json

# Count items by field
jq 'group_by(.status) | map({key: .[0].status, value: length}) | from_entries' events.json

# Merge two JSON files
jq -s '.[0] * .[1]' base.json override.json

# Slurp multiple JSON lines (NDJSON) into array
jq -s '.' < ndjson.txt

# Filter NDJSON stream without slurping (memory-efficient)
jq -c 'select(.level == "error")' < ndjson.txt

# Compact all objects in array
jq -c '.[]' data.json

# Update a field in-place (print modified JSON)
jq '.version = "2.0"' package.json

# Delete a key
jq 'del(.password)' user.json

# Rename a key
jq '{new_name: .old_name} + del(.old_name)' obj.json

# Add element to array
jq '.tags += ["new-tag"]' item.json

# Get Docker container IPs
docker inspect $(docker ps -q) | jq '.[].NetworkSettings.Networks[].IPAddress' -r

# Parse GitHub API response
curl -s "https://api.github.com/repos/torvalds/linux/releases" \
  | jq -r '.[0] | "Latest: \(.tag_name) β€” \(.published_at)"'

# AWS CLI output processing
aws ec2 describe-instances | jq -r \
  '.Reservations[].Instances[] | "\(.InstanceId)\t\(.State.Name)\t\(.PublicIpAddress // "N/A")"'

Debugging#

jq '. | debug'                   # print to stderr + pass through
jq '. | debug("msg: \(.key)")'   # custom debug message (jq 1.6+)
jq 'error("bad input")'          # raise an error
jq 'halt_error(1)'               # exit with code

[!TIP] Use jq -n (null input) to build JSON from scratch without needing an input file: jq -n '{name: "Alice", ts: now | todate}'. The now builtin returns the current Unix timestamp.