Why jq list contructor and object constructor behaves differently for generators?

Viewed 101

I want to get the Cartesian product of 2 lists, and tried following way:

echo [[1,2,3],[4,5,6]] | jq '[.[0][],.[1][]]'

I expected to get [[1,4],[1,5],[1,6],[2,4],[2,5],....], but what I really get is [1,2,3,4,5,6]

But using the following 2 commands, I get the Cartesian product output,

echo [[1,2,3],[4,5,6]] | jq '(.[0][] | tostring) + "," + (.[1][] | tostring)'
echo [[1,2,3],[4,5,6]] | jq '{"x": .[0][], "y": .[1][]}'

My questions is: why does comma behave differently from "+"? why does list constructor behave differently from object constructor?

1 Answers

Because thats how the , operator in jq works. When two filters are separated by , then the same input will be fed into both and the output value streams will be concatenated in order. In your case, both the filters return the corresponding array elements at indices 0 and 1 respectively and the result is collected into an array.

As for Object construction, the manual makes it clear that,

If one of the expressions produces multiple results, multiple dictionaries will be produced.

On an input

{"user":"stedolan","titles":["JQ Primer", "More JQ"]}

the expression

{user, title: .titles[]}

produces two outputs, one for each value in the array titles

{"user":"stedolan", "title": "JQ Primer"}
{"user":"stedolan", "title": "More JQ"}

So going back to your original attempts,

  1. In the first case, each expression runs on the original input array separately and the results are just combined together into the bigger one
  2. The third case falls into the case of object creation, when one of the expression produces multiple results. Since both your expressions generate multiple results, your result dictionary is formed as cartesian product of the two arrays
  3. The second case is almost similar to 3), but the operations happen over string types i.e. each integer type converted to string before the cartesian product is generated. Note that this produces a result that is neither a list or a dictionary

You can still generate cartesian product on the arrays, when you do this

.[0][] as $x | .[1][] as $y | [$x,$y]

and put the whole thing into an array if you put the above filter inside [..] as

[.[0][] as $x | .[1][] as $y | [$x,$y]]

The reason this works, is because of Variable binding operator with the syntax expression as $variable.

The expression exp as $x | ... means for each value of expression exp, run the rest of the pipeline with the entire original input, and with $x set to that value. Thus as functions as something of a foreach loop. So with our example, for each value in $x and each value in $y, we form the result [$x, $y], which will be the resulting cartesian product.

Related