jq — Construction

Build JSON from Shell Commands

Construct JSON using --arg for string values
jq -n --arg name "$(hostname)" --arg kernel "$(uname -r)" \
    '{"hostname": $name, "kernel": $kernel}'
# Output:
# {
#   "hostname": "modestus-razer",
#   "kernel": "6.19.10-arch1-1"
# }

--arg name value binds a shell string as a jq variable $name. Always use --arg for strings — it handles quoting and escaping.

Construct JSON using --argjson for numbers and booleans
jq -n --arg host "$(hostname)" --argjson cpus "$(nproc)" --argjson mem "$(free -m | awk '/Mem:/{print $2}')" \
    '{"host": $host, "cpus": $cpus, "memory_mb": $mem}'
# Output:
# {
#   "host": "modestus-razer",
#   "cpus": 16,
#   "memory_mb": 31805
# }

--argjson parses the value as JSON — use it for numbers, booleans, and nested structures. --arg always produces a string.

Build array from shell loop
for iface in $(ls /sys/class/net/); do
    jq -n --arg name "$iface" --arg state "$(cat /sys/class/net/$iface/operstate 2>/dev/null || echo unknown)" \
        '{"name": $name, "state": $state}'
done | jq -s '.'
# Output: array of interface objects with name and state

The pattern: emit one JSON object per loop iteration, then jq -s '.' slurps them into an array.

Generate JSON with -n

Create JSON from nothing (no input)
jq -n '{"created": now | strftime("%Y-%m-%dT%H:%M:%SZ"), "version": "1.0"}'
# Output:
# {
#   "created": "2026-04-11T...",
#   "version": "1.0"
# }

-n (null input) tells jq not to read stdin. Required when generating JSON rather than transforming it.

Build an array literal
jq -n '[range(5)]'
# Output: [0, 1, 2, 3, 4]
Build a sequence of objects
jq -n '[range(3) | {id: ., label: "item-\(.)"}]'
# Output:
# [
#   {"id": 0, "label": "item-0"},
#   {"id": 1, "label": "item-1"},
#   {"id": 2, "label": "item-2"}
# ]

Object Construction

Build new objects from existing data
echo '{"ifname":"eth0","address":"aa:bb:cc:dd:ee:ff","operstate":"UP","mtu":1500,"txqlen":1000}' | \
    jq '{name: .ifname, mac: .address, up: (.operstate == "UP")}'
# Output:
# {
#   "name": "eth0",
#   "mac": "aa:bb:cc:dd:ee:ff",
#   "up": true
# }

Object construction {key: expr} creates a new object. The expression after the colon is evaluated in the current context.

Shorthand — key name matches field name
echo '{"name":"eth0","state":"UP","mtu":1500,"flags":["BROADCAST","MULTICAST"]}' | \
    jq '{name, state}'
# Output:
# {
#   "name": "eth0",
#   "state": "UP"
# }

When the key name matches the field name, {name} is shorthand for \{name: .name}.

Computed key names with parentheses
echo '[{"key":"dns","value":"10.0.0.53"},{"key":"ntp","value":"10.0.0.123"}]' | \
    jq '[.[] | {(.key): .value}] | add'
# Output:
# {
#   "dns": "10.0.0.53",
#   "ntp": "10.0.0.123"
# }

Parentheses around the key position evaluate an expression as the key name. add merges the array of single-key objects into one.

to_entries / from_entries

Convert object to key-value pairs
echo '{"dns":"53","http":"80","https":"443"}' | jq 'to_entries'
# Output:
# [
#   {"key": "dns", "value": "53"},
#   {"key": "http", "value": "80"},
#   {"key": "https", "value": "443"}
# ]

to_entries explodes an object into an array of {key, value} pairs. This lets you filter, map, and sort object properties as if they were array elements.

Filter entries, then convert back
echo '{"dns":"53","http":"80","https":"443","ssh":"22","telnet":"23"}' | \
    jq '[to_entries[] | select(.value | tonumber > 50)] | from_entries'
# Output:
# {
#   "http": "80",
#   "https": "443"
# }

from_entries is the inverse — it takes [{key, value}] and builds an object.

Rename keys using with_entries
echo '{"ifname":"eth0","operstate":"UP","address":"aa:bb:cc:dd:ee:ff"}' | \
    jq 'with_entries(
        if .key == "ifname" then .key = "name"
        elif .key == "operstate" then .key = "state"
        elif .key == "address" then .key = "mac"
        else . end
    )'
# Output:
# {
#   "name": "eth0",
#   "state": "UP",
#   "mac": "aa:bb:cc:dd:ee:ff"
# }

with_entries(f) is shorthand for to_entries | map(f) | from_entries. Use it to transform keys, filter fields, or modify values while preserving object structure.

Add and Merge

Merge two objects
echo '{"name":"eth0"}' | jq '. + {"state":"UP","mtu":1500}'
# Output:
# {
#   "name": "eth0",
#   "state": "UP",
#   "mtu": 1500
# }

+ on objects performs a shallow merge. Right side wins on key conflicts.

Deep merge with *
echo '[{"name":"eth0","config":{"mtu":1500}},{"name":"eth0","config":{"speed":"1G"}}]' | \
    jq '.[0] * .[1]'
# Output:
# {
#   "name": "eth0",
#   "config": {
#     "mtu": 1500,
#     "speed": "1G"
#   }
# }

* performs recursive merge — nested objects are merged rather than replaced.

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

Update Existing Fields

Update a field value
echo '{"name":"eth0","state":"DOWN","mtu":1500}' | jq '.state = "UP"'
# Output:
# {
#   "name": "eth0",
#   "state": "UP",
#   "mtu": 1500
# }
Update with |= (update operator)
echo '{"name":"eth0","mtu":1500}' | jq '.mtu |= . * 2'
# Output:
# {
#   "name": "eth0",
#   "mtu": 3000
# }

|= passes the current value of the field through the expression on the right. .mtu |= . * 2 means "take the current mtu, double it, write it back."

Add a field to every object in an array
echo '[{"name":"eth0"},{"name":"lo"}]' | jq '[.[] | . + {"monitored": true}]'
# Output:
# [
#   {"name": "eth0", "monitored": true},
#   {"name": "lo", "monitored": true}
# ]
Delete a field
echo '{"name":"eth0","state":"UP","internal_id":"x9f2"}' | jq 'del(.internal_id)'
# Output:
# {
#   "name": "eth0",
#   "state": "UP"
# }