Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JQ: Reduce array of objects to object, adding to array

I've got a more complex JQ expression that's handling an array of objects.

The input looks like this:

[
    { "key": "1", "value": "value 1"},
    { "key": "2", "value": "value 2"},
    { "key": "1", "value": "value 3"},
]

What I want to get is this:

{
    "1": { "values": ["value 1", "value 3"] },
    "2": { "values": ["value 2"] }
}

or, for my use case:

{
    "1": [ "value 1", "value 3" ],
    "2": [ "value 2" ]
}

would also be OK.

I've already tried to use … | { (.key): [.value] } but the result is (to no real surprise to me) that later occurrences of keys simply overwrite already existing ones. What I want to accomplish is something like "create a new key/value pair or add .value to an already existing one's 'values' array".

like image 718
Alex Avatar asked Oct 12 '16 09:10

Alex


2 Answers

The drawback of a solution relying on group_by is that group_by requires a sort, which is unnecessary here. In this response, I'll show how to avoid any sorting by using a generic (and generally useful) jq function that "melds" an array of JSON objects, essentially by popping the value at each key into an array, and then concatenating corresponding arrays.

# input should be an array of objects
def meld:
  reduce .[] as $o
    ({}; reduce ($o|keys)[] as $key (.; .[$key] += [$o[$key]] ));

Let's also define some data:

def data:
[
    { "key": "1", "value": "value 1"},
    { "key": "2", "value": "value 2"},
    { "key": "1", "value": "value 3"}
]
;

Then the filter:

data |  map([.] | from_entries) | meld

produces:

{"1":["value 1","value 3"],"2":["value 2"]}
like image 165
peak Avatar answered Oct 12 '22 16:10

peak


OK, after finally finding out what I wanted I also understand that my previous filters didn't keep the input array but resulted in objects being output after each other. So that was basically the reason why all examples I found wouldn't work.

I wanted to group by keys (hence the key/value requirement), which group_by already does, but wouldn't work.

From grouping working its only a small step to my solution (unique keys, values in arrays).

'… group_by(.key) | map({ "key": .[0].key, "values": map(.value) | unique })'

The output now looks like this, which is perfectly fine for my requirements:

[
    {
        "key": "1",
        "values": [
            "value 1",
            "value 3"
        ],
    },
    {
        "key": "2",
        "values": [
            "value 2"
        ]
    }
]
like image 33
Alex Avatar answered Oct 12 '22 17:10

Alex