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"

empty

Produces no output:

echo '[1,2,3]' | jq '.[] | if . > 2 then . else empty end'
# 3

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}

limit / first / until

limit

echo '[1,2,3,4,5,6,7,8,9,10]' | jq '[limit(3; .[])]'
# [1,2,3]

first

echo '[1,2,3,4,5]' | jq 'first(.[] | select(. > 3))'
# 4

until

echo '1' | jq 'until(. > 100; . * 2)'
# 128

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

  1. map() for transformation - Apply to each element

  2. select() for filtering - Keep matching items

  3. group_by() for aggregation - Group then process

  4. reduce for accumulation - Custom aggregation

  5. def for reusability - Define custom functions

  6. Chain functions - Build complex pipelines

Next Module

Infrastructure Patterns - Real-world jq for APIs, configs, and logs.