jq — Transforms

map

Apply a transformation to every element
echo '[{"name":"eth0","mtu":1500},{"name":"lo","mtu":65536}]' | \
    jq 'map({interface: .name, mtu_kb: (.mtu / 1024 | floor)})'
# Output:
# [
#   {"interface": "eth0", "mtu_kb": 1},
#   {"interface": "lo", "mtu_kb": 64}
# ]

map(f) is shorthand for [.[] | f]. It applies f to each element and collects results back into an array.

map with select — filter and transform in one step
echo '[{"name":"eth0","state":"UP"},{"name":"lo","state":"UNKNOWN"},{"name":"wlan0","state":"UP"}]' | \
    jq 'map(select(.state == "UP") | .name)'
# Output: ["eth0", "wlan0"]
map_values — transform values, preserve keys
echo '{"dns":"53","http":"80","https":"443"}' | \
    jq 'map_values(tonumber)'
# Output:
# {
#   "dns": 53,
#   "http": 80,
#   "https": 443
# }

map_values(f) applies f to each value of an object, keeping keys intact. map(f) works on arrays; map_values(f) works on both arrays and objects.

reduce

Accumulate a value across elements
echo '[1,2,3,4,5]' | jq 'reduce .[] as $x (0; . + $x)'
# Output: 15

reduce .[] as $x (init; update) — start with init (0), for each element $x, apply update (add $x to accumulator).

Build an object from an array using reduce
echo '[{"key":"dns","port":53},{"key":"http","port":80},{"key":"ssh","port":22}]' | \
    jq 'reduce .[] as $item ({}; . + {($item.key): $item.port})'
# Output:
# {
#   "dns": 53,
#   "http": 80,
#   "ssh": 22
# }
Running total with reduce
echo '[100,200,150,300]' | \
    jq '[foreach .[] as $x (0; . + $x)]'
# Output: [100, 300, 450, 750]

foreach is like reduce but emits each intermediate value. Use it for running totals, cumulative sums, or state machines.

recurse

Find all values at any depth
echo '{"a":{"b":{"c":"deep"},"d":"shallow"},"e":"top"}' | \
    jq '[recurse | strings]'
# Output: ["deep", "shallow", "top"]

recurse descends into every nested structure. Combined with type filters (strings, numbers, booleans), it extracts all values of a given type regardless of depth.

Find all keys at any depth
echo '{"a":{"b":1,"c":{"d":2}},"e":3}' | \
    jq '[path(..) | .[-1] | select(type == "string")]'
# Output: ["a", "b", "c", "d", "e"]
Find all numbers in a nested structure
echo '{"config":{"timeout":30,"retry":{"count":3,"delay":5}},"name":"test"}' | \
    jq '[recurse | numbers]'
# Output: [30, 3, 5]

walk

Transform all strings to uppercase
echo '{"name":"eth0","config":{"mode":"auto","state":"up"}}' | \
    jq 'walk(if type == "string" then ascii_upcase else . end)'
# Output:
# {
#   "name": "ETH0",
#   "config": {
#     "mode": "AUTO",
#     "state": "UP"
#   }
# }

walk(f) applies f bottom-up to every value in the structure. It handles recursion for you — just specify the transformation for leaf values.

Sanitize nulls throughout a structure
echo '{"name":"eth0","ip":null,"config":{"gateway":null,"dns":"10.0.0.53"}}' | \
    jq 'walk(if . == null then "N/A" else . end)'
# Output:
# {
#   "name": "eth0",
#   "ip": "N/A",
#   "config": {
#     "gateway": "N/A",
#     "dns": "10.0.0.53"
#   }
# }
Round all numbers
echo '{"cpu":45.678,"mem":82.123,"disk":{"used":67.891}}' | \
    jq 'walk(if type == "number" then (. * 10 | floor / 10) else . end)'
# Output:
# {
#   "cpu": 45.6,
#   "mem": 82.1,
#   "disk": {
#     "used": 67.8
#   }
# }

Path Expressions

Get the path to every leaf value
echo '{"a":{"b":1},"c":[2,3]}' | jq '[path(..)]'
# Output:
# [
#   [],
#   ["a"],
#   ["a","b"],
#   ["c"],
#   ["c",0],
#   ["c",1]
# ]
Get value at a specific path with getpath
echo '{"a":{"b":{"c":"found"}}}' | jq 'getpath(["a","b","c"])'
# Output: "found"
Set value at a path with setpath
echo '{"a":{"b":1}}' | jq 'setpath(["a","c"]; 42)'
# Output:
# {
#   "a": {
#     "b": 1,
#     "c": 42
#   }
# }
Find paths matching a condition
echo '{"a":1,"b":{"c":2,"d":{"e":3}}}' | \
    jq '[path(.. | numbers) | join(".")]'
# Output: ["a", "b.c", "b.d.e"]

env and $ENV

Access environment variables inside jq
export MY_HOST="web-01"
echo '{}' | jq --arg h "$MY_HOST" '{host: $h}'
# Output: {"host": "web-01"}
Using $ENV for environment access
echo 'null' | jq -n 'env.HOME'
# Output: "/home/evan"

echo 'null' | jq -n 'env.SHELL'
# Output: "/usr/bin/zsh"

env.NAME and $ENV.NAME both access environment variables. env is the preferred form.

Build config from environment
export APP_PORT=8080
export APP_HOST=localhost
export APP_DEBUG=true
jq -n '{port: (env.APP_PORT | tonumber), host: env.APP_HOST, debug: (env.APP_DEBUG == "true")}'
# Output:
# {
#   "port": 8080,
#   "host": "localhost",
#   "debug": true
# }

String Transforms

String manipulation builtins
echo '"  Hello, World!  "' | jq '{
    trimmed: ltrimstr("  ") | rtrimstr("  "),
    upper: ascii_upcase,
    lower: ascii_downcase,
    length: length
}'
# Output:
# {
#   "trimmed": "Hello, World!",
#   "upper": "  HELLO, WORLD!  ",
#   "lower": "  hello, world!  ",
#   "length": 17
# }
gsub — global regex replacement
echo '"eth0: 10.50.1.100/24 scope global"' | jq 'gsub("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"; "X.X.X.X")'
# Output: "eth0: X.X.X.X/24 scope global"
capture — named regex groups
echo '"error: connection refused at 10.0.0.1:443"' | \
    jq 'capture("(?<ip>[0-9.]+):(?<port>[0-9]+)")'
# Output:
# {
#   "ip": "10.0.0.1",
#   "port": "443"
# }

capture with named groups is jq’s equivalent of Python’s re.match().group(). It returns an object with group names as keys.

scan — find all matches
echo '"servers: 10.0.0.1, 10.0.0.2, 10.0.0.3"' | \
    jq '[scan("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+")]'
# Output: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]

Type Conversions

Number to string and back
echo '1500' | jq 'tostring'
# Output: "1500"

echo '"1500"' | jq 'tonumber'
# Output: 1500
ASCII conversions
echo '65' | jq 'implode'
# Output: "A"

echo '"A"' | jq 'explode'
# Output: [65]
Type-safe arithmetic
echo '{"port":"8080","offset":"100"}' | \
    jq '(.port | tonumber) + (.offset | tonumber)'
# Output: 8180

Always convert with tonumber before arithmetic. JSON APIs frequently return numbers as strings — jq will error on "8080" + 100.