zwerch
zwerch

Reputation: 73

Merge two complex JSON objects (using jq)

I'm trying to replace objects in a complex JSON object. It seemed that the tool jq could be offering the perfect solution, but I'm really struggling with the right choice / chain of filters.

I have a complete configuration JSON object which looks like this (has some more keys in it, shortened it for illustration):

{
    "some-array": [
        {
            "name": "foo",
            "attr": "value"
        },
        {
            "name": "foo bar",
            "attr": "value"
        },
        {
            "name": "foo bar baz",
            "attr": "value"
        }
    ],
    "some-other-array": []
}

Now I have another object containing an array with updated objects which I need to merge with the full configuration in some way. I need to find the nested objects by name, add it if it does not exist yet and replace it if it does exist.

{
    "some-array": [
        {
            "name": "foo",
            "attr": "new-value",
            "new-attrib": "new-value"
        },
        {
            "name": "foo bar",
            "attr": "new-value"
        }
    ]
}

So, with the above example, my expected result would be:

{
    "some-array": [
        {
            "name": "foo",
            "attr": "new-value",
            "new-attrib": "new-value"
        },
        {
            "name": "foo bar",
            "attr": "new-value"
        },
        {
            "name": "foo bar baz",
            "attr": "value"
        }
    ],
    "some-other-array": []
}

I already tried select(."some-array"[].name == "foo") to begin with and a few other things as a jq filter, but I'm struggling to move forward here and would really appreciate some inspiration / an actual solution.

Can anyone tell me if what I'm trying to achieve is actually possible with jq or do I have to find another solution?

Upvotes: 1

Views: 1003

Answers (2)

peak
peak

Reputation: 117077

Yes, it's possible, and in fact quite easy under various interpretations of the problem as originally stated.

The following solves the the problem as it was originally stated, with "it" being interpreted as .["some-array"] rather than its constituents.

Assuming $update holds the object with the updated information as shown, the update could be performed using this filter:

.["some-array"] = ($update | .["some-array"])

There are many ways to endow $update with the desired value.

Upvotes: 1

peak
peak

Reputation: 117077

Here is a solution to the updated problem. This solution assumes that the names are string-valued. It relies on two helper functions:

# array-to-hash
def a2h(f): reduce .[] as $x ({}; . + {($x | f): $x});

# hash-to-array
def h2a: . as $in | reduce keys_unsorted[] as $k ([]; . + [$in[$k]]);

The first of these creates a "hash" based on an input array, and the second implements the inverse operation.

With these helper functions, the solution can be written:

.["some-array"] |= (a2h(.name) + ($update|.["some-array"] | a2h(.name)) | h2a)

where $update is the "new" value. This solution relies on the "right-dominance" of object-addition.

Output

For the given example, the output is:

{
  "some-array": [
    {
      "name": "foo",
      "attr": "new-value",
      "new-attrib": "new-value"
    },
    {
      "name": "foo bar",
      "attr": "new-value"
    },
    {
      "name": "foo bar baz",
      "attr": "value"
    }
  ],
  "some-other-array": []
}

Upvotes: 1

Related Questions