Generators, backtracking and empty

jq · Expert · 12 min

Every 'why did I get four results?' or 'why did nothing print?' puzzle in jq comes from its generator semantics.

Every jq expression is a generator: for one input it can emit zero, one or many outputs. .[] emits many, select emits zero or one, empty emits none, and , concatenates generators.

When an operator combines two generators, jq evaluates all combinations. That gives cartesian products:

  • (1,2) + (10,20) outputs four values: 11, 12, 21, 22 (the right operand varies in the outer loop).
  • {a: (1,2), b: (3,4)} produces four objects.
  • [.[] | .x] | length is safe, but .[].x + .[].y combines every x with every y, a common accidental blow-up.

empty is meaningful: in if cond then . else empty end it removes the value, and select(f) is defined as if f then . else empty end. A function that produces empty inside [...] simply contributes nothing.

Under the hood jq backtracks: after emitting an output downstream, it resumes the generator for the next one. limit(n; f) and first(f) stop a generator early using label $out | ... break $out, which you can use yourself to exit a loop.

range(n), range(from; upto; by), recurse(f), repeat and until(cond; next) are generator building blocks. For loops with state prefer reduce/foreach, which run in constant stack, over deep recursive defs.

Example

jq -nc '[(1,2) + (10,20)]'                  # [11,12,21,22]
jq -nc '{a: (1,2), b: (3,4)} | [.a,.b]'      # [1,3] [1,4] [2,3] [2,4]
jq -nc '[1,2,3] | [.[] | if . % 2 == 1 then . else empty end]'   # [1,3]
jq -nc '[range(0; 10; 3)]'                   # [0,3,6,9]
jq -nc '[limit(3; repeat(1))]'               # [1,1,1]
jq -nc '[label $out | range(10) | if . == 4 then break $out else . end]'  # [0,1,2,3]
jq -nc '[1 | recurse(if . < 50 then . * 3 else empty end)]'      # [1,3,9,27,81]
jq -nc '[2 | until(. > 100; . * 2)]'         # [128]

Remember

  • Expressions emit zero, one or many outputs
  • Binary operators combine all output pairs (cartesian product)
  • empty emits nothing; label/break and limit stop generators early

Watch out for

  • Combining two .[] expressions in one arithmetic or object expression and getting N x M results.

Check yourself

  1. How many outputs does `(1,2,3) * (1,2)` produce?
  2. What does `[1, empty, 2]` evaluate to?

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

Official docs: jqlang.org/manual/

Builds on

Unlocks

Nothing builds on this yet.