jq Operators

jq provides a rich set of operators for comparing values, performing arithmetic, combining conditions, and manipulating strings.

Comparison Operators

Equality

# Equal
echo '{"status": "active"}' | jq '.status == "active"'  # true

# Not equal
echo '{"status": "active"}' | jq '.status != "inactive"'  # true

Numeric Comparison

echo '{"port": 443}' | jq '.port > 1024'   # false
echo '{"port": 443}' | jq '.port < 1024'   # true
echo '{"port": 443}' | jq '.port >= 443'   # true
echo '{"port": 443}' | jq '.port <= 443'   # true

Type Comparison

# Check type
echo '"hello"' | jq 'type'           # "string"
echo '123' | jq 'type'               # "number"
echo 'true' | jq 'type'              # "boolean"
echo '[]' | jq 'type'                # "array"
echo '{}' | jq 'type'                # "object"
echo 'null' | jq 'type'              # "null"

# Type testing
echo '"hello"' | jq 'type == "string"'  # true

Arithmetic Operators

Basic Math

echo '{"a": 10, "b": 3}' | jq '.a + .b'   # 13
echo '{"a": 10, "b": 3}' | jq '.a - .b'   # 7
echo '{"a": 10, "b": 3}' | jq '.a * .b'   # 30
echo '{"a": 10, "b": 3}' | jq '.a / .b'   # 3.333...
echo '{"a": 10, "b": 3}' | jq '.a % .b'   # 1 (modulo)

String Concatenation

echo '{"first": "John", "last": "Doe"}' | jq '.first + " " + .last'
# "John Doe"

Array Concatenation

echo '{"a": [1,2], "b": [3,4]}' | jq '.a + .b'
# [1,2,3,4]

Object Merge

echo '{"a": {"x": 1}, "b": {"y": 2}}' | jq '.a + .b'
# {"x": 1, "y": 2}

# Right side wins on conflicts
echo '{}' | jq '{"a": 1} + {"a": 2}'
# {"a": 2}

Increment/Modify

echo '{"count": 5}' | jq '.count += 1'   # {"count": 6}
echo '{"count": 5}' | jq '.count -= 1'   # {"count": 4}
echo '{"count": 5}' | jq '.count *= 2'   # {"count": 10}

Logical Operators

And / Or / Not

# AND
echo '{"a": true, "b": true}' | jq '.a and .b'   # true
echo '{"a": true, "b": false}' | jq '.a and .b'  # false

# OR
echo '{"a": false, "b": true}' | jq '.a or .b'   # true
echo '{"a": false, "b": false}' | jq '.a or .b'  # false

# NOT
echo 'true' | jq 'not'   # false
echo 'false' | jq 'not'  # true

Truthiness

In jq, false and null are falsy. Everything else is truthy.

echo '0' | jq 'if . then "truthy" else "falsy" end'        # "truthy"
echo '""' | jq 'if . then "truthy" else "falsy" end'       # "truthy"
echo '[]' | jq 'if . then "truthy" else "falsy" end'       # "truthy"
echo 'null' | jq 'if . then "truthy" else "falsy" end'     # "falsy"
echo 'false' | jq 'if . then "truthy" else "falsy" end'    # "falsy"

String Operations

Length

echo '"hello"' | jq 'length'  # 5

Split and Join

# Split string into array
echo '"a,b,c"' | jq 'split(",")'  # ["a","b","c"]

# Join array into string
echo '["a","b","c"]' | jq 'join(",")'  # "a,b,c"

Contains and Inside

# String contains
echo '"hello world"' | jq 'contains("world")'  # true

# Array contains
echo '["a","b","c"]' | jq 'contains(["b"])'  # true

# Inside (reverse of contains)
echo '"world"' | jq 'inside("hello world")'  # true

Starts/Ends With

echo '"server-01"' | jq 'startswith("server")'  # true
echo '"server-01"' | jq 'endswith("-01")'       # true

Case Conversion

echo '"Hello World"' | jq 'ascii_downcase'  # "hello world"
echo '"Hello World"' | jq 'ascii_upcase'    # "HELLO WORLD"

Replace (gsub/sub)

# Replace all occurrences
echo '"foo bar foo"' | jq 'gsub("foo"; "baz")'  # "baz bar baz"

# Replace first occurrence
echo '"foo bar foo"' | jq 'sub("foo"; "baz")'   # "baz bar foo"

# With regex
echo '"server-01"' | jq 'gsub("-[0-9]+"; "")'   # "server"

Test (Regex Match)

# Test if matches regex
echo '"192.168.1.1"' | jq 'test("^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$")'  # true
echo '"not-an-ip"' | jq 'test("^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$")'    # false

Match (Capture Groups)

# Extract capture groups
echo '"server-web-01"' | jq 'match("server-(.+)-([0-9]+)")'

Output:

{
  "offset": 0,
  "length": 13,
  "string": "server-web-01",
  "captures": [
    {"offset": 7, "length": 3, "string": "web", "name": null},
    {"offset": 11, "length": 2, "string": "01", "name": null}
  ]
}

Named Captures

echo '"server-web-01"' | jq 'capture("server-(?<role>.+)-(?<num>[0-9]+)")'

Output:

{
  "role": "web",
  "num": "01"
}

IN and INDEX

IN (Membership Test)

# Check if value is in a set
echo '"critical"' | jq 'IN("critical", "high", "medium")'  # true
echo '"low"' | jq 'IN("critical", "high", "medium")'       # false

INDEX (Build Lookup Table)

# Create object from array using field as key
echo '[{"id":"a","val":1},{"id":"b","val":2}]' | jq 'INDEX(.id)'

Output:

{
  "a": {"id": "a", "val": 1},
  "b": {"id": "b", "val": 2}
}

Null Handling

Alternative Operator //

Provides default value when left side is null or false:

echo '{"name": "server"}' | jq '.port // 22'
# 22 (port is null, use default)

echo '{"port": 443}' | jq '.port // 22'
# 443 (port exists, use it)

echo '{"port": 0}' | jq '.port // 22'
# 0 (port is 0, NOT null, use it)

echo '{"enabled": false}' | jq '.enabled // true'
# true (false is falsy, use default)

Error Suppression ?

# Without ?: error on null
echo 'null' | jq '.foo.bar'
# null

# With ?: suppress error
echo 'null' | jq '.foo?.bar?'
# null (no error)

# On arrays (would error without ?)
echo '{"items": null}' | jq '.items[]?'
# (no output, no error)

Math Functions

# Floor, ceil, round
echo '3.7' | jq 'floor'   # 3
echo '3.2' | jq 'ceil'    # 4
echo '3.5' | jq 'round'   # 4

# Absolute value
echo '-5' | jq 'fabs'     # 5

# Square root
echo '16' | jq 'sqrt'     # 4

# Logarithms
echo '100' | jq 'log10'   # 2
echo '2.718' | jq 'log'   # ~1

# Power (requires computation)
echo '{"base": 2, "exp": 8}' | jq 'pow(.base; .exp)'  # 256

# Min/max in array
echo '[5, 2, 8, 1, 9]' | jq 'min'  # 1
echo '[5, 2, 8, 1, 9]' | jq 'max'  # 9

Practice Exercises

Sample Data

cat << 'EOF' > /tmp/logs.json
[
  {"timestamp": "2026-03-15T10:00:00Z", "severity": "critical", "message": "Auth failed", "count": 5},
  {"timestamp": "2026-03-15T10:01:00Z", "severity": "info", "message": "User login", "count": 100},
  {"timestamp": "2026-03-15T10:02:00Z", "severity": "high", "message": "Config changed", "count": 2},
  {"timestamp": "2026-03-15T10:03:00Z", "severity": "low", "message": "Heartbeat", "count": 1000}
]
EOF

Tasks

  1. Find logs where count > 10:

    jq '.[] | select(.count > 10)' /tmp/logs.json
  2. Check if any severity is critical:

    jq 'any(.[]; .severity == "critical")' /tmp/logs.json
  3. Calculate total count:

    jq '[.[].count] | add' /tmp/logs.json
  4. Find logs with "fail" in message (case insensitive):

    jq '.[] | select(.message | ascii_downcase | contains("fail"))' /tmp/logs.json
  5. Extract severity if high or critical:

    jq '.[] | select(.severity | IN("high", "critical")) | .message' /tmp/logs.json

Operator Precedence

From highest to lowest:

  1. . (field access)

  2. [] (array/object access)

  3. | (pipe)

  4. *, /, % (multiplication, division, modulo)

  5. +, - (addition, subtraction)

  6. ==, !=, <, >, , >= (comparison)

  7. and

  8. or

  9. // (alternative)

Use parentheses to override:

# Without parens
echo '5' | jq '. + 3 * 2'      # 11 (3*2 first, then +5)

# With parens
echo '5' | jq '(. + 3) * 2'    # 16 ((5+3) first, then *2)

Key Takeaways

  1. == for comparison - Not = (that’s assignment/update)

  2. and/or for logic - Not &&/||

  3. // for defaults - Essential for null handling

  4. IN() for membership - Cleaner than chained or

  5. Regex with test/match - Full regex support

  6. String ops are powerful - split, join, gsub

Next Module

Transformations - Object construction and array manipulation.