4

I have a dictionary keyed on usernames, like:

"userinfo": { "alice": { "key1": 1, "key2": 2}, "bob": { "key1": 11, "key2": 22}, ... }

I want to display tabular text of the username and some values.

alice 1 2
bob 11 22

It's easy enough to get the usernames into the first column OR to pull the values out (.info[]|[.key1,.key2]) into the later columns, but I can't figure out how to get both the usernames and the values under them in the same jq command. I am trying to avoid doing something silly like running jq multiple times and pasting the output.

Kusalananda
  • 333,661

2 Answers2

11

You can use following command to do it:

jq -r '.userinfo | to_entries[] | [.key, .value.key1, .value.key2] | @tsv'

You first convert the data to entries so you can reference them, then make a list with your values and at the end you convert it into a tab-seperated-values output with the @tsv operator. If you want it to be another format, you can read more about it here.

Also use the -r flag to get raw output, otherwise you would get the "\t" character :)


Comment from @0stone0: If you have an unknown number of key*'s, you can use [ .key, .value[] ] to get them all

Bog
  • 989
  • Amazing! Thank you. That is exactly what I need, I was unfamiliar with to_entries[] but it is going to be handy for a lot of odd cases. – Chip Seraphine Mar 19 '24 at 15:07
  • 1
    Yeah jq is a really really powerful tool. It's like learning a whole new programming language. Whenever I think I learned everything about it there are still some new things :) – Bog Mar 19 '24 at 15:09
7
$ jq -r '.userinfo | keys[] as $k | [ $k, .[$k][] ] | @tsv' file
alice   1       2
bob     11      22

This assumes that the input in file is a valid JSON document, such as

{
  "userinfo": {
    "alice": {
      "key1": 1,
      "key2": 2
    },
    "bob": {
      "key1": 11,
      "key2": 22
    }
  }
}

The jq expression picks out the userinfo top-level object and then iterates over the keys of this object, with each key being assigned to the internal variable $k. It is specifically the [] of keys[] that causes the looping over all the keys (usernames).

For each iteration, the array [ $k, .[$k][] ] is constructed, i.e. an array consisting of the key itself followed by the values in the userinfo object associated with that particular key (regardless of what the keys under .[$k] might be or how many there may be, i.e. we ignore the keys key1 and key2 themselves).

The constructed array is then passed through the @tsv operator which outputs it as a tab-delimited record.

For this to make any sense, we assume that the keys are ordered the same way for each object under userinfo. If that's not the case, you may want to sort the keys by passing the data through jq -S . first. If sub-objects have a varying set of keys (some may have a key3 and others may lack key1), then the output is still unlikely to make sense as you have no header on the fields in the output and no indication that a key is missing in an object.

We can take care of generating headers for each field based on the key names in the sub-objects (and using name as the header for the username field) with a somewhat more complicated jq expression:

(
    [.userinfo[] | keys[]] | unique
) as $h |

This creates the header using "name" and the unique keys

from each sub-object:

[ "name", $h[] ],

Now we need a "double loop" over each user object and

the unique keys (in $h) that we extracted previously,

basically: for each user, create an array consisting of

the username and the value for each of the keys

(the inner loop):

[ .userinfo as $u | $u |

# Outer loop:
keys[] as $k | 
[
    # The username:
    $k,
    # Inner loop over the $h array, extracting
    # either useful data or nothing depending on
    # whether the key exists in this object.
    ($h[] | $u[$k][.])

] ] | @tsv

Example input with unsorted and missing/extra keys:

{
  "userinfo": {
    "alice": {
      "key2": 2,
      "key1": 1
    },
    "bob": {
      "key1": 11,
      "key2": 22
    },
    "mallory": {
      "key1": 111,
      "key3": 333
    }
  }
}

Output:

name    key1    key2    key3
alice   1       2
bob     11      22
mallory 111             333
Kusalananda
  • 333,661