jq Transformations

Transformations are where jq shines. Reshape JSON into exactly the structure you need - flatten, nest, merge, split, or completely reconstruct data.

Object Construction

Build New Objects

echo '{"firstName": "John", "lastName": "Doe", "age": 30, "city": "LA"}' | jq '{
  name: (.firstName + " " + .lastName),
  location: .city
}'

Output:

{
  "name": "John Doe",
  "location": "LA"
}

Shorthand Syntax

When key matches field name:

echo '{"name": "server", "ip": "10.0.0.1", "port": 22, "extra": "ignored"}' | jq '{name, ip, port}'

Output:

{
  "name": "server",
  "ip": "10.0.0.1",
  "port": 22
}

Dynamic Keys

echo '{"key": "hostname", "value": "server-01"}' | jq '{(.key): .value}'

Output:

{
  "hostname": "server-01"
}

Nested Object Construction

echo '{"src_ip": "10.0.0.1", "src_port": 443, "dst_ip": "10.0.0.2", "dst_port": 80}' | jq '{
  source: {ip: .src_ip, port: .src_port},
  destination: {ip: .dst_ip, port: .dst_port}
}'

Output:

{
  "source": {"ip": "10.0.0.1", "port": 443},
  "destination": {"ip": "10.0.0.2", "port": 80}
}

Array Construction

Collect Values

# Collect iterated values into array
echo '[{"name":"a"},{"name":"b"},{"name":"c"}]' | jq '[.[].name]'

Output: ["a","b","c"]

Array of Objects

echo '{"users": ["alice", "bob"]}' | jq '[.users[] | {username: ., role: "user"}]'

Output:

[
  {"username": "alice", "role": "user"},
  {"username": "bob", "role": "user"}
]

Map

Apply transformation to each array element:

# Simple map
echo '[1,2,3]' | jq 'map(. * 2)'
# [2,4,6]

# Map with object construction
echo '[{"name":"a","val":1},{"name":"b","val":2}]' | jq 'map({id: .name, value: .val})'

Output:

[
  {"id": "a", "value": 1},
  {"id": "b", "value": 2}
]

Map Values (Objects)

Transform object values:

echo '{"a": 1, "b": 2, "c": 3}' | jq 'map_values(. * 10)'

Output: {"a": 10, "b": 20, "c": 30}

Select / Filter

Basic Filter

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

echo '[{"status":"active"},{"status":"inactive"}]' | jq '[.[] | select(.status == "active")]'
# [{"status":"active"}]

Map + Select

echo '[{"name":"a","active":true},{"name":"b","active":false}]' | jq '
  map(select(.active)) | map(.name)
'
# ["a"]

Negative Filter

echo '[{"severity":"info"},{"severity":"critical"}]' | jq '[.[] | select(.severity != "info")]'
# [{"severity":"critical"}]

Flatten

Nested Arrays

echo '[[1,2],[3,[4,5]]]' | jq 'flatten'
# [1,2,3,4,5]

# Flatten one level
echo '[[1,2],[3,[4,5]]]' | jq 'flatten(1)'
# [1,2,3,[4,5]]

Objects with Arrays

echo '{"zones": [{"name": "inside", "ips": ["10.0.0.1", "10.0.0.2"]}, {"name": "dmz", "ips": ["172.16.0.1"]}]}' | jq '
  [.zones[] | .ips[]]
'
# ["10.0.0.1","10.0.0.2","172.16.0.1"]

Group By

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 + Transform

echo '[
  {"type":"web","name":"web-01"},
  {"type":"db","name":"db-01"},
  {"type":"web","name":"web-02"}
]' | jq '
  group_by(.type) | map({
    type: .[0].type,
    servers: [.[].name],
    count: length
  })
'

Output:

[
  {"type": "db", "servers": ["db-01"], "count": 1},
  {"type": "web", "servers": ["web-01", "web-02"], "count": 2}
]

Sort

# Sort numbers
echo '[3,1,4,1,5,9]' | jq 'sort'
# [1,1,3,4,5,9]

# Sort strings
echo '["charlie","alice","bob"]' | jq 'sort'
# ["alice","bob","charlie"]

# Sort by field
echo '[{"name":"c","val":1},{"name":"a","val":3},{"name":"b","val":2}]' | jq 'sort_by(.name)'
# sorted by name alphabetically

# Reverse sort
echo '[1,2,3]' | jq 'sort | reverse'
# [3,2,1]

Unique

# Unique values
echo '[1,2,2,3,3,3]' | jq 'unique'
# [1,2,3]

# Unique by field
echo '[{"type":"a"},{"type":"b"},{"type":"a"}]' | jq 'unique_by(.type)'
# [{"type":"a"},{"type":"b"}]

Add / Reduce

Sum

echo '[1,2,3,4,5]' | jq 'add'
# 15

echo '[{"val":1},{"val":2},{"val":3}]' | jq '[.[].val] | add'
# 6

Concatenate Arrays

echo '[["a","b"],["c","d"]]' | jq 'add'
# ["a","b","c","d"]

Reduce (Advanced)

# Manual sum with reduce
echo '[1,2,3,4,5]' | jq 'reduce .[] as $x (0; . + $x)'
# 15

# Build object from array
echo '[{"key":"a","val":1},{"key":"b","val":2}]' | jq '
  reduce .[] as $item ({}; . + {($item.key): $item.val})
'
# {"a":1,"b":2}

To/From Entries

to_entries

Convert object to array of key-value pairs:

echo '{"name":"server","ip":"10.0.0.1"}' | jq 'to_entries'

Output:

[
  {"key": "name", "value": "server"},
  {"key": "ip", "value": "10.0.0.1"}
]

from_entries

Convert array of key-value pairs to object:

echo '[{"key":"name","value":"server"},{"key":"ip","value":"10.0.0.1"}]' | jq 'from_entries'

Output: {"name":"server","ip":"10.0.0.1"}

with_entries

Transform object via entries:

# Uppercase all keys
echo '{"name":"server","ip":"10.0.0.1"}' | jq 'with_entries(.key |= ascii_upcase)'

Output: {"NAME":"server","IP":"10.0.0.1"}

# Prefix all values
echo '{"a":"1","b":"2"}' | jq 'with_entries(.value = "prefix_" + .value)'

Output: {"a":"prefix_1","b":"prefix_2"}

Delete Keys

# Delete single key
echo '{"a":1,"b":2,"c":3}' | jq 'del(.b)'
# {"a":1,"c":3}

# Delete multiple keys
echo '{"a":1,"b":2,"c":3}' | jq 'del(.a, .c)'
# {"b":2}

# Delete nested key
echo '{"user":{"name":"jdoe","password":"secret"}}' | jq 'del(.user.password)'
# {"user":{"name":"jdoe"}}

Paths

Get All Paths

echo '{"a":{"b":1},"c":[2,3]}' | jq '[paths]'

Output:

[
  ["a"],
  ["a","b"],
  ["c"],
  ["c",0],
  ["c",1]
]

Get/Set by Path

# Get value at path
echo '{"a":{"b":{"c":123}}}' | jq 'getpath(["a","b","c"])'
# 123

# Set value at path
echo '{"a":{"b":{"c":123}}}' | jq 'setpath(["a","b","c"]; 456)'
# {"a":{"b":{"c":456}}}

Recursive Descent ..

Find values at any depth:

echo '{"a":{"ip":"10.0.0.1"},"b":{"c":{"ip":"10.0.0.2"}}}' | jq '.. | .ip? // empty'

Output:

"10.0.0.1"
"10.0.0.2"

Practice: Log Normalization

Input (ISE Log)

{
  "ise_timestamp": "2026-03-15T14:30:45Z",
  "user_name": "jdoe",
  "user_domain": "CHLA",
  "calling_station_id": "AA:BB:CC:DD:EE:FF",
  "framed_ip_address": "10.50.10.100",
  "passed": false,
  "failure_reason": "Certificate expired"
}

Transform to OCSF

jq '{
  time: .ise_timestamp,
  category_uid: 3,
  class_uid: 3001,
  activity_id: (if .passed then 1 else 2 end),
  status: (if .passed then "Success" else "Failure" end),
  status_detail: .failure_reason,
  actor: {
    user: {
      name: .user_name,
      domain: .user_domain
    }
  },
  device: {
    mac: .calling_station_id,
    ip: .framed_ip_address
  },
  metadata: {
    product: {name: "Cisco ISE", vendor_name: "Cisco"},
    version: "1.0.0"
  }
}'

Output (OCSF Format)

{
  "time": "2026-03-15T14:30:45Z",
  "category_uid": 3,
  "class_uid": 3001,
  "activity_id": 2,
  "status": "Failure",
  "status_detail": "Certificate expired",
  "actor": {
    "user": {
      "name": "jdoe",
      "domain": "CHLA"
    }
  },
  "device": {
    "mac": "AA:BB:CC:DD:EE:FF",
    "ip": "10.50.10.100"
  },
  "metadata": {
    "product": {"name": "Cisco ISE", "vendor_name": "Cisco"},
    "version": "1.0.0"
  }
}

Key Takeaways

  1. {} for object construction - Build exact structure needed

  2. [] to collect - Wrap iteration in [] to create array

  3. map() for batch transforms - Apply to every element

  4. select() for filtering - Keep matching elements

  5. group_by() for aggregation - Group by field

  6. to_entries/from_entries - Transform object structure

  7. del() to remove - Clean up unwanted fields

Next Module

Conditionals - if-then-else, select, and alternative operator.