Search code examples
jsonjqtranspose

Zip lists in jq's objects construction by {} instead of multiplying them like default


A JSON object like this:

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

And, I want to convert it with lists(assume all lists have equal length N) zipped and output like this:

{"user":"stedolan","title":"JQ Primer","year":2013}
{"user":"stedolan","title":"More JQ","year":2016}

I followed Object - {} example and tried:

tmp='{"user":"stedolan","titles":["JQ Primer", "More JQ"],"years":[2013, 2016]}'
echo $tmp | jq '{user, title: .titles[], year: .years[]}'

then it output:

{"user":"stedolan","title":"JQ Primer","year":2013}
{"user":"stedolan","title":"JQ Primer","year":2016}
{"user":"stedolan","title":"More JQ","year":2013}
{"user":"stedolan","title":"More JQ","year":2016}

It produces N*N ... lines result, instead of N lines result.

Any suggestion is appreciated!


Solution

  • transpose/0 can be used to effectively zip the values together. And the nice thing about the way assignments work is that it can be assigned simultaneously over multiple variables.

    ([.titles,.years]|transpose[]) as [$title,$year] | {user,$title,$year}
    

    If you want the results in an array rather than a stream, just wrap it all in [].

    https://jqplay.org/s/ZIFU5gBnZ7


    For a jq 1.4 compatible version, you'll have to rewrite it to not use destructuring but you could use the same transpose/0 implementation from the builtins.

    transpose/0:

    def transpose:
      if . == [] then []
      else . as $in
      | (map(length) | max) as $max
      | length as $length
      | reduce range(0; $max) as $j
          ([]; . + [reduce range(0;$length) as $i ([]; . + [ $in[$i][$j] ] )] )
                end;
    

    Here's an alternative implementation that I cooked up that should also be compatible. :)

    def transpose2:
        length as $cols
          | (map(length) | max) as $rows
          | [range(0;$rows) as $r | [.[range(0;$cols)][$r]]];
    
    ([.titles,.years]|transpose[]) as $p | {user,title:$p[0],year:$p[1]}