skip to Main Content

I want to merge two JSON files, where the objects in the base file (A) are augmented by matching objects in file (B), but not overwritten where A and B have different values for the same key.

For example, I have a JSON file (A) with contents like

[
  {
    "name": "alpha",
    "value": "apple"
  },
  {
    "name": "beta",
    "value": "banana"
  },
  {
    "name": "gamma",
    "value": "guava"
  }
]

and a second JSON file (B), with the same basic structure but optionally with additional keys (metadata in this example, but it could be anything):

[
  {
    "name": "alpha",
    "value": "apple",
    "metadata": {
      "types": [
        "granny"
      ]
    }
  },
  {
    "name": "beta",
    "value": "blueberry"
  },
  {
    "name": "omega",
    "value": "orange"
  }
]

I want to merge the two lists together, key/values from objects in B are added into the same object (matched by name) in A. My JQ to achieve this is like:

jq -s '[ .[0][] as $a | .[1][] as $b | select ($a.name == $b.name) | $a * $b ]' b.json a.json

This works to add the metadata into A and retains A’s values for other keys. However, gamma is missing from the result. From the example above, I want a result where:

  • alpha should have the metadata
  • beta should be banana, not blueberry.
  • gamma should be in the final list
  • omega should NOT be in the final list
  • Order of objects in the list should be maintained.

I want this:

[
  {
    "name": "alpha",
    "value": "apple"
    "metadata": {
      "types": [
        "granny"
      ]
    }
  },
  {
    "name": "beta",
    "value": "banana"
  },
  {
    "name": "gamma",
    "value": "guava"
  }
]

How do I adapt this query to also include top level objects from A, but not from B?

2

Answers


  1. If names are guaranteed to be unique within one file, you could temporarily turn file A into an INDEX, so checking the containedness of a .name becomes a matter of checking keys with has. Then, iterate over the items of file B using reduce on input[], and if a name is present as key, update the according field by adding its old value to the current item (order matters). Finally, revert the object structure back to an array by collecting all field values into an array, using [.[]].

    jq 'reduce input[] as $i (INDEX(.name);
      if has($i.name) then .[$i.name] |= $i + . end
    ) | [.[]]' fileA.json fileB.json
    
    [
      {
        "name": "alpha",
        "value": "apple",
        "metadata": {
          "types": [
            "granny"
          ]
        }
      },
      {
        "name": "beta",
        "value": "banana"
      },
      {
        "name": "gamma",
        "value": "guava"
      }
    ]
    

    Demo

    Login or Signup to reply.
  2. Try this: jq ‘map(. + (input | map(select(.name == .name)))[0])’ jsonA.json jsonB.json

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search