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#
| Option | Meaning |
|---|---|
-r | Raw output (unquoted strings) |
-c | Compact output (no pretty print) |
-n | Null input (create JSON without stdin) |
-e | Exit 1 if last output is false/null |
-s | Slurp: read all inputs into an array |
-R | Raw input: treat input as raw strings |
-j | Join output (like -r but no trailing newline) |
--arg NAME VAL | Pass shell variable as string |
--argjson NAME VAL | Pass shell variable as JSON |
--slurpfile NAME FILE | Load file as JSON into variable |
--tab | Use tabs for indentation |
--indent N | Use 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}'. Thenowbuiltin returns the current Unix timestamp.