jq — Transforms
map
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.
echo '[{"name":"eth0","state":"UP"},{"name":"lo","state":"UNKNOWN"},{"name":"wlan0","state":"UP"}]' | \
jq 'map(select(.state == "UP") | .name)'
# Output: ["eth0", "wlan0"]
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
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).
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
# }
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
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.
echo '{"a":{"b":1,"c":{"d":2}},"e":3}' | \
jq '[path(..) | .[-1] | select(type == "string")]'
# Output: ["a", "b", "c", "d", "e"]
echo '{"config":{"timeout":30,"retry":{"count":3,"delay":5}},"name":"test"}' | \
jq '[recurse | numbers]'
# Output: [30, 3, 5]
walk
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.
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"
# }
# }
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
echo '{"a":{"b":1},"c":[2,3]}' | jq '[path(..)]'
# Output:
# [
# [],
# ["a"],
# ["a","b"],
# ["c"],
# ["c",0],
# ["c",1]
# ]
echo '{"a":{"b":{"c":"found"}}}' | jq 'getpath(["a","b","c"])'
# Output: "found"
echo '{"a":{"b":1}}' | jq 'setpath(["a","c"]; 42)'
# Output:
# {
# "a": {
# "b": 1,
# "c": 42
# }
# }
echo '{"a":1,"b":{"c":2,"d":{"e":3}}}' | \
jq '[path(.. | numbers) | join(".")]'
# Output: ["a", "b.c", "b.d.e"]
env and $ENV
export MY_HOST="web-01"
echo '{}' | jq --arg h "$MY_HOST" '{host: $h}'
# Output: {"host": "web-01"}
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.
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
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
# }
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"
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.
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
echo '1500' | jq 'tostring'
# Output: "1500"
echo '"1500"' | jq 'tonumber'
# Output: 1500
echo '65' | jq 'implode'
# Output: "A"
echo '"A"' | jq 'explode'
# Output: [65]
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.