jq Cookbook
jq steps in a Script Task pipeline are run against the full variable map serialized as a JSON object. The step’s output must itself be an object — every key it emits is merged back into the variable map. Non-object output is a step error.
The engine runs filters via jaq-interpret, which implements a strict subset of jq. Sandbox-incompatible features (@sh, input, IO functions) are not registered.
For the syntax cheat-sheet, see the official jq manual. What follows are the patterns that come up most often in Conduit pipelines.
Reading and reshaping
1. Pick a single field
{ doc_no: .grn.header.docNo }
2. Pick several fields
{ doc_no: .grn.header.docNo, vendor: .grn.header.vendor, ts: .grn.header.timestamp }
3. Rename keys
{ orderId: .order.id, customerId: .order.buyer.id }
4. Pass-through with overrides
Keep every existing variable, but set or replace a few:
. + { status: "validated", checked_at: now }
. + { … } does shallow object merge; the right-hand side wins on conflicts.
5. Subset of keys
Keep only orderId and total:
{ orderId, total }
Shorthand: { key } is { key: .key }.
6. Omit a key
jq has no built-in “omit”; build the object explicitly or use with_entries:
with_entries(select(.key != "secret_token"))
7. Promote a nested field to the top
. + { customerEmail: .customer.contact.email }
8. Default for a missing field
{ retries: (.retries // 0) + 1 }
a // b returns b if a is null or false.
9. Coalesce a chain
{ name: (.preferred_name // .legal_name // "Anonymous") }
Arrays
10. Map over an array
{ totals: [ .items[] | .price * .qty ] }
.items[] streams each element, [ … ] collects back into an array.
11. Filter an array
{ active: [ .users[] | select(.disabled_at == null) ] }
12. Sum a field
{ subtotal: ([ .items[] | .price * .qty ] | add) }
add sums a list. An empty list adds to null, so guard if needed: ([ … ] | add) // 0.
13. Group by
{ by_country: ( .customers | group_by(.country) ) }
14. Sort by
{ recent: ( .orders | sort_by(.created_at) | reverse ) }
15. Unique by
{ vendors: ( .lines | map(.vendor) | unique ) }
16. Min and max by
{ smallest: ( .quotes | min_by(.amount) ), largest: ( .quotes | max_by(.amount) ) }
17. Top N
{ top_three: ( .items | sort_by(-.score) | .[0:3] ) }
18. Flatten one level
{ all_tags: ( [ .posts[].tags ] | add ) }
Conditionals
19. Conditional value
{ tier: ( if .total >= 1000 then "gold" else "silver" end ) }
20. Multi-branch tier
{ tier: (
if .total >= 5000 then "platinum"
elif .total >= 1000 then "gold"
elif .total >= 100 then "silver"
else "bronze"
end
) }
21. Set a field only when missing
. + (if .correlation_id == null then { correlation_id: ($ENV.HOSTNAME + "-" + (now|tostring)) } else {} end)
Strings
22. String interpolation
{ ref: "GRN-\(.vendor)-\(.doc_no)" }
23. Uppercase / lowercase
{ tag: ( .vendor | ascii_upcase ) }
24. Split and trim
{ first_word: ( .description | split(" ") | .[0] ) }
25. Regex test and capture
{ is_email: ( .raw | test("^[^@]+@[^@]+\\.[^@]+$") ) }
{ parts: ( .raw | capture("^(?<user>[^@]+)@(?<host>.+)$") ) }
From-entries / to-entries
26. Convert object to list of pairs
{ pairs: ( .counts | to_entries ) }
Yields [ { key: "...", value: ... }, ... ].
27. Build an object from a list of pairs
{ scores: ( [ .results[] | { key: .id, value: .score } ] | from_entries ) }
28. Filter object entries
{ public: ( .config | with_entries(select(.key | startswith("public_"))) ) }
Error handling
29. Optional path access
? swallows the “not an object/array” error so the step still returns a value:
{ city: ( .customer.address.city? // "Unknown" ) }
30. Try with fallback
{ port: ( try (.config.port | tonumber) catch 8080 ) }
Math and dates
31. Round to two decimals
{ rate_pct: ( ((.received / .ordered) * 10000 | floor) / 100 ) }
32. Epoch seconds → ISO date
{ today_iso: ( now | todate ) }
Putting it together
A two-step pipeline that pulls fields out of a nested object, then computes a derived total:
Step 1 (jq):
{ lines: [ .order.lines[] | { sku, qty, price, line_total: (.qty * .price) } ] }
Step 2 (jq):
{ subtotal: ( [ .lines[].line_total ] | add ),
line_count: ( .lines | length ) }
After running, the instance has lines, subtotal, and line_count set.
See also
- Script Task — how pipeline steps merge their output back into variables
- Expressions (FEEL) — FEEL cookbook for the same pipeline
- Rhai Cookbook — when jq + FEEL aren’t enough
- jq manual — full language reference