jq Functions
jq provides powerful built-in functions and lets you define custom ones. Master these for efficient data processing pipelines.
Essential Built-in Functions
length
echo '"hello"' | jq 'length' # 5 (string chars)
echo '[1,2,3]' | jq 'length' # 3 (array elements)
echo '{"a":1,"b":2}' | jq 'length' # 2 (object keys)
echo 'null' | jq 'length' # null
keys / keys_unsorted
echo '{"z":1,"a":2,"m":3}' | jq 'keys' # ["a","m","z"] (sorted)
echo '{"z":1,"a":2,"m":3}' | jq 'keys_unsorted' # ["z","a","m"] (original order)
echo '[10,20,30]' | jq 'keys' # [0,1,2] (indices)
values
echo '{"a":1,"b":2,"c":3}' | jq '[.[] | values]' # [1,2,3]
has
echo '{"name":"server"}' | jq 'has("name")' # true
echo '{"name":"server"}' | jq 'has("port")' # false
echo '[1,2,3]' | jq 'has(2)' # true (index 2 exists)
type
echo '"hello"' | jq 'type' # "string"
echo '123' | jq 'type' # "number"
echo 'true' | jq 'type' # "boolean"
echo 'null' | jq 'type' # "null"
echo '[]' | jq 'type' # "array"
echo '{}' | jq 'type' # "object"
Array Functions
first / last / nth
echo '[1,2,3,4,5]' | jq 'first' # 1
echo '[1,2,3,4,5]' | jq 'last' # 5
echo '[1,2,3,4,5]' | jq 'nth(2)' # 3
reverse
echo '[1,2,3]' | jq 'reverse' # [3,2,1]
sort / sort_by
echo '[3,1,4,1,5]' | jq 'sort' # [1,1,3,4,5]
echo '[{"name":"c","val":1},{"name":"a","val":3}]' | jq 'sort_by(.name)'
# [{"name":"a","val":3},{"name":"c","val":1}]
echo '[{"name":"c","val":1},{"name":"a","val":3}]' | jq 'sort_by(.val)'
# [{"name":"c","val":1},{"name":"a","val":3}]
unique / unique_by
echo '[1,2,2,3,3,3]' | jq 'unique' # [1,2,3]
echo '[{"type":"a","id":1},{"type":"b","id":2},{"type":"a","id":3}]' | jq 'unique_by(.type)'
# [{"type":"a","id":1},{"type":"b","id":2}]
flatten
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]]
min / max
echo '[5,2,8,1,9]' | jq 'min' # 1
echo '[5,2,8,1,9]' | jq 'max' # 9
# By field
echo '[{"name":"a","val":3},{"name":"b","val":1}]' | jq 'min_by(.val)'
# {"name":"b","val":1}
add
echo '[1,2,3,4,5]' | jq 'add' # 15
echo '["a","b","c"]' | jq 'add' # "abc"
echo '[[1,2],[3,4]]' | jq 'add' # [1,2,3,4]
echo '[{"a":1},{"b":2}]' | jq 'add' # {"a":1,"b":2}
echo '[]' | jq 'add' # null
echo '[]' | jq 'add // 0' # 0 (with default)
any / all
echo '[1,2,3,4,5]' | jq 'any(. > 3)' # true
echo '[1,2,3,4,5]' | jq 'all(. > 0)' # true
echo '[1,2,3,4,5]' | jq 'all(. > 3)' # false
# With generator
echo '[{"active":true},{"active":false}]' | jq 'any(.[]; .active)' # true
echo '[{"active":true},{"active":true}]' | jq 'all(.[]; .active)' # true
contains / inside
echo '[1,2,3]' | jq 'contains([2])' # true
echo '{"a":1,"b":2}' | jq 'contains({"a":1})' # true
echo '[2]' | jq 'inside([1,2,3])' # true
map
Apply expression to each element:
echo '[1,2,3]' | jq 'map(. * 2)' # [2,4,6]
echo '[{"name":"a"},{"name":"b"}]' | jq 'map(.name)' # ["a","b"]
echo '[{"a":1,"b":2},{"a":3,"b":4}]' | jq 'map({x: .a, y: .b})'
# [{"x":1,"y":2},{"x":3,"y":4}]
map_values
Transform object values:
echo '{"a":1,"b":2,"c":3}' | jq 'map_values(. * 10)'
# {"a":10,"b":20,"c":30}
map with select
echo '[1,2,3,4,5]' | jq 'map(select(. > 2))' # [3,4,5]
echo '[{"status":"active"},{"status":"inactive"}]' | jq '
map(select(.status == "active"))
'
# [{"status":"active"}]
group_by
Group array elements by field:
echo '[
{"type":"web","name":"web-01"},
{"type":"db","name":"db-01"},
{"type":"web","name":"web-02"}
]' | jq 'group_by(.type)'
Output:
[
[{"type":"db","name":"db-01"}],
[{"type":"web","name":"web-01"},{"type":"web","name":"web-02"}]
]
Group + Aggregate
echo '[
{"severity":"high","count":5},
{"severity":"low","count":10},
{"severity":"high","count":3}
]' | jq '
group_by(.severity) |
map({
severity: .[0].severity,
total: map(.count) | add,
events: length
})
'
Output:
[
{"severity": "high", "total": 8, "events": 2},
{"severity": "low", "total": 10, "events": 1}
]
reduce
Accumulate values:
# Syntax: reduce EXPR as $VAR (INIT; UPDATE)
# Sum
echo '[1,2,3,4,5]' | jq 'reduce .[] as $x (0; . + $x)'
# 15
# Build object
echo '[{"k":"a","v":1},{"k":"b","v":2}]' | jq '
reduce .[] as $item ({}; . + {($item.k): $item.v})
'
# {"a":1,"b":2}
# Find max
echo '[3,1,4,1,5,9,2,6]' | jq 'reduce .[] as $x (0; if $x > . then $x else . end)'
# 9
Complex Reduce
echo '[
{"type":"web","bytes":100},
{"type":"db","bytes":200},
{"type":"web","bytes":150}
]' | jq '
reduce .[] as $item ({};
.[$item.type] = ((.[$item.type] // 0) + $item.bytes)
)
'
# {"web":250,"db":200}
range
Generate numbers:
echo 'null' | jq '[range(5)]' # [0,1,2,3,4]
echo 'null' | jq '[range(2;5)]' # [2,3,4]
echo 'null' | jq '[range(0;10;2)]' # [0,2,4,6,8]
String Functions
split / join
echo '"a,b,c"' | jq 'split(",")' # ["a","b","c"]
echo '["a","b","c"]' | jq 'join(",")' # "a,b,c"
ltrimstr / rtrimstr
echo '"prefix_value"' | jq 'ltrimstr("prefix_")' # "value"
echo '"value_suffix"' | jq 'rtrimstr("_suffix")' # "value"
test / match / capture
# Test regex
echo '"192.168.1.1"' | jq 'test("^[0-9.]+")' # true
# Match with groups
echo '"server-web-01"' | jq 'match("server-(.+)-([0-9]+)")'
# {"offset":0,"length":13,"string":"server-web-01","captures":[...]}
# Named capture groups
echo '"server-web-01"' | jq 'capture("server-(?<role>.+)-(?<num>[0-9]+)")'
# {"role":"web","num":"01"}
gsub / sub
echo '"foo bar foo"' | jq 'gsub("foo";"baz")' # "baz bar baz"
echo '"foo bar foo"' | jq 'sub("foo";"baz")' # "baz bar foo"
Date/Time Functions
now
echo 'null' | jq 'now'
# 1710510045.123 (Unix timestamp)
echo 'null' | jq 'now | todate'
# "2026-03-15T14:00:45Z"
Date Parsing
# Parse ISO date to timestamp
echo '"2026-03-15T14:30:45Z"' | jq 'fromdate'
# 1710512445
# Format timestamp
echo '1710512445' | jq 'todate'
# "2026-03-15T14:30:45Z"
# Custom format
echo '1710512445' | jq 'strftime("%Y-%m-%d")'
# "2026-03-15"
Custom Functions (def)
Basic Definition
echo '10' | jq 'def double: . * 2; double'
# 20
echo '[1,2,3]' | jq 'def double: . * 2; map(double)'
# [2,4,6]
With Parameters
echo '10' | jq 'def multiply(x): . * x; multiply(5)'
# 50
echo '"hello"' | jq 'def wrap(prefix; suffix): prefix + . + suffix; wrap("<<"; ">>")'
# "<<hello>>"
Reusable Transforms
echo '{"event":"auth_failure","severity":"high"}' | jq '
def add_mitre(technique; name):
. + {"mitre": {"id": technique, "name": name}};
def route_to_sentinel:
. + {"route": "sentinel"};
if .event == "auth_failure" then
add_mitre("T1078"; "Valid Accounts") | route_to_sentinel
else
. + {"route": "s3"}
end
'
Library Pattern
# Store in file
cat << 'EOF' > /tmp/lib.jq
def is_security_event:
.severity | IN("critical", "high") or
.event | IN("auth_failure", "intrusion", "config_change");
def add_timestamp:
. + {"processed_at": (now | todate)};
def normalize:
{
time: .timestamp,
type: .event,
level: .severity,
data: .
};
EOF
# Use library
echo '{"timestamp":"2026-03-15T14:30:45Z","event":"auth_failure","severity":"high"}' | \
jq -f /tmp/lib.jq 'add_timestamp | normalize'
Practical Patterns
Count by Field
echo '[
{"status":"success"},
{"status":"failure"},
{"status":"success"},
{"status":"success"}
]' | jq '
group_by(.status) |
map({status: .[0].status, count: length}) |
sort_by(.count) |
reverse
'
Output:
[
{"status": "success", "count": 3},
{"status": "failure", "count": 1}
]
Pivot Table
echo '[
{"region":"us","product":"a","sales":100},
{"region":"eu","product":"a","sales":150},
{"region":"us","product":"b","sales":200}
]' | jq '
group_by(.region) |
map({
region: .[0].region,
products: (group_by(.product) | map({
product: .[0].product,
total: map(.sales) | add
}))
})
'
Top N by Field
echo '[
{"name":"a","score":85},
{"name":"b","score":92},
{"name":"c","score":78},
{"name":"d","score":95}
]' | jq '
sort_by(.score) | reverse | .[0:2]
'
Output:
[
{"name": "d", "score": 95},
{"name": "b", "score": 92}
]
Key Takeaways
-
map()for transformation - Apply to each element -
select()for filtering - Keep matching items -
group_by()for aggregation - Group then process -
reducefor accumulation - Custom aggregation -
deffor reusability - Define custom functions -
Chain functions - Build complex pipelines
Next Module
Infrastructure Patterns - Real-world jq for APIs, configs, and logs.