3

I would like to use jq to turn an array of objects into an object of arrays.

Consider if I have the following two files:

file1.json:

{
  "key1": 5,
  "key2": 10
}

file2.json:

{
  "key1": 2
}

I would like to merge them together to form:

{
  "key1": [5, 2]
  "key2": [10, null]
}

It's easy to do this with one field per jq command, but I can't figure out how to do it with all the fields at once. My thought is that I need to convert all the values to arrays and then use reduce with *, but I couldn't get it to work.

The jq command needs to work for an arbitrary number of files (more than 2).

3 Answers 3

2

The following will merge an array of objects into a single object as you describe (in particular, null is used as a filler, which certainly makes sense if, for example, each input object is regarded as an "observation"), but please note that merge/0 as defined here makes no assumptions about the sets of keys in the input objects, and could potentially be made much faster depending on the assumptions that could be made.

def merge:
  def allkeys: map(keys) | add | unique;
  allkeys as $allkeys
  | reduce .[] as $in ({};
     reduce $allkeys[] as $k (.;
       . + {($k): (.[$k] + [$in[$k]]) } ));

merge

Now use the "slurp" option, e.g.:

 $ jq -s -f merge.jq file*.json
Sign up to request clarification or add additional context in comments.

2 Comments

How would you get this to work with an arbitrary number of files?
@schmmd -- Please note that the original version of merge/0 was not sufficiently general.
2

This is probably slower than @peak's solution, but maybe easier to read:

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

Use it like:

jq -s 'map(to_entries) | flatten(1) | group_by(.key) | map({key: .[0].key, value: map(.value)}) | from_entries' file*.json

Unlike your desired output, it does not put nulls in place of missing values, which may be useful if "null" is a valid value in itself.

Comments

0

Here is another solution.

  (add | keys) as $all                                                  # get all keys
| map( . as $o | reduce $all[] as $k ({}; .[$k] = $o[$k]) )             # normalize objects
|    ( . as $a | reduce $all[] as $k ({}; .[$k] = ($a | map(.[$k]))) )  # transpose

To run it use the -s option, e.g:

$ jq -s -f filter.jq file1.json file2.json

Sample output:

{
  "key1": [
    5,
    2
  ],
  "key2": [
    10,
    null
  ]
}

If you have more files, e.g. file3.json

{
  "key3": 2
}

just add them to the command line

$ jq -s -f filter.jq file1.json file2.json file3.json

Sample output:

{
  "key1": [
    5,
    2,
    null
  ],
  "key2": [
    10,
    null,
    null
  ],
  "key3": [
    null,
    null,
    2
  ]
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.