jq: idiomatically aggregate object keys by value

Viewed 79

Let's say I have a JSON object that looks like:

{
    "Person A": "Accounting",
    "Person B": "Accounting",
    "Person C": "Production",
    "Person D": "Production",
    "Person E": "Marketing"
}

And I'd like to flip it inside out so that for each Department, I have a list of people in it, a la:

{
    "Accounting": [
        "Person A",
        "Person B"
    ],
    "Production": [
        "Person C",
        "Person D"
    ],
    "Marketing": [
        "Person E"
    ]
}

I'm still pretty new to jq and have come up with a couple of functional solutions, but each has their own issues and I'm hoping there's a better, more idiomatic way to do it that I just haven't figured out yet.

jq program 1 (jqplay):

to_entries
| group_by(.value)
| map( { "key": .[0].value, "value": [ .[].key ] } )
| from_entries

The inefficiency here is that the structure created by group_by creates a structure that needs to be further manipulated, so for each entry in the resulting list, we build our own by taking the first subentry's value (ignoring every other subentry's value), and then taking every subentry's key.

jq program 2 (jqplay)

to_entries | reduce .[] as $x (
    {};
    . * {
        ($x.value): (
            (.[$x.value]//[]) + [ $x.key ]
        )
    }
)

Here we're using reduce to efficiently build the thing we want to build which is nice. However, the * doesn't recursively + arrays, so we have to handle that part ourselves, and that makes for some pretty hairy reading (at least from my perspective).

Can you think of a more elegant and/or idiomatic way to aggregate an object's values by key in jq?

1 Answers

You don't need the * operator there. Something like this should work just fine:

reduce to_entries[] as {$key, $value} ({};
  .[$value] += [$key]
)

Online demo

Related