Variables, reduce and defining functions

jq · Advanced · 12 min

Complex transformations, like joining datasets or building lookup tables, need variables, folds and helpers.

Variables: expr as $x | body evaluates body with $x bound to each output of expr. It is how you keep an outer value while descending: .team as $t | .members[] | {team: $t, name}. Destructuring works too: . as {name: $n, tags: [$first]} | ....

reduce folds a stream into one value: reduce .[] as $item (init; update). Inside update, . is the accumulator.

  • Sum: reduce .[] as $x (0; . + $x).
  • Build a lookup object: reduce .[] as $u ({}; .[$u.id | tostring] = $u.name).

foreach is similar but outputs each intermediate state, useful for running totals.

Functions are defined with def name: body; or with parameters def name(f): body;. Parameters are filters, not values: def twice(f): f | f;. Prefix a parameter with $ to receive a value instead: def add_tax($rate): . * (1 + $rate);. Functions can be recursive and can be kept in a file loaded with -L dir and import "name" as n; or include.

A powerful join pattern combines these: load one file into a lookup object with --slurpfile or INDEX(.[]; .id), then enrich the other: INDEX(users[]; .id) as $u | orders[] | .user = $u[.user_id | tostring].name.

Example

echo '[3,5,7]' | jq 'reduce .[] as $x (0; . + $x)'          # 15
echo '[1,2,3]' | jq -c '[foreach .[] as $x (0; . + $x)]'    # [1,3,6] running total
echo '{"team":"core","members":[{"name":"Ann"},{"name":"Bob"}]}' |
  jq -c '.team as $t | .members[] | {team: $t, name}'
# {"team":"core","name":"Ann"}  {"team":"core","name":"Bob"}
echo '[100,250]' | jq -c 'def add_tax($r): . * (1 + $r); map(add_tax(0.25))'   # [125,312.5]
echo '[1,2]' | jq -c 'def twice(f): f | f; map(twice(. * 10))'                 # [100,200]
cat > d.json <<'EOF'
{"users":[{"id":1,"name":"Ann"},{"id":2,"name":"Bob"}],"orders":[{"oid":7,"user_id":2}]}
EOF
jq -c 'INDEX(.users[]; .id) as $u | .orders[] | .user = $u[.user_id | tostring].name' d.json
# {"oid":7,"user_id":2,"user":"Bob"}

Remember

  • expr as $x | body binds variables
  • reduce .[] as $x (init; update) folds a stream
  • def f(g): ...; takes filters; def f($v): takes values

Watch out for

  • Expecting a function parameter without $ to be a value; it is a filter evaluated against the current input.

Check yourself

  1. What does `reduce .[] as $x (1; . * $x)` return for `[2,3,4]`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: jqlang.org/manual/