Guidelines

This site is for tech Q&A. Please keep your posts focused on the subject at hand.

Ask one question at a time. Don't conflate multiple problems into a single question.

Make sure to include all relevant information in your posts. Try to avoid linking to external sites.

Links to documentation are fine, but in addition you should also quote the relevant parts in your posts.

0 votes
3.0k views
3.0k views

I have a JSON object like this:

{
  "a": {
    "b": "xx",
    "c": {
      "d": "xx",
      "e": "xx"
    }
  }
}

where I need to modify several nested values (for example "b" and "e"), so that the result would look like this:

{
  "a": {
    "b": "yy",
    "c": {
      "d": "xx",
      "e": "yy"
    }
  }
}

However, if I try adding an object with the updated values I end up with everything else removed:

me@localhost:~ $ json='{"a":{"b":"xx","c":{"d":"xx","e":"xx"}}}'
me@localhost:~ $ echo "$json" | jq -r '. + {"a":{"b":"yy","c":{"e":"yy"}}}'
{
  "a": {
    "b": "yy",
    "c": {
      "e": "yy"
    }
  }
}

And when I modify the values sequentially I get two objects with one property changed each:

me@localhost:~ $ echo "$json" | jq -r '. | .a.b |= "yy", .a.c.e |= "yy"'
{
  "a": {
    "b": "yy",
    "c": {
      "d": "xx",
      "e": "xx"
    }
  }
}
{
  "a": {
    "b": "xx",
    "c": {
      "d": "xx",
      "e": "yy"
    }
  }
}
in Scripting
edited by
by (115)
2 18 33
edit history

Please log in or register to answer this question.

1 Answer

0 votes
 

Your first approach doesn't work because jq by default does not do a deep merge when adding an object, so you end up replacing the current nested object in "a" with the new one. From the documentation:

Addition: +

The operator + takes two filters, applies them both to the same input, and adds the results together. What "adding" means depends on the types involved:
[...]

  • Objects are added by merging, that is, inserting all the key-value pairs from both objects into a single combined object. If both objects contain a value for the same key, the object on the right of the + wins. (For recursive merge use the * operator.)

Your second approach doesn't work because the comma operator feeds the input stream into all comma-separated filters individually, so you end up with multiple results. From the documentation:

Comma: ,

If two filters are separated by a comma, then the same input will be fed into both and the two filters' output value streams will be concatenated in order: first, all of the outputs produced by the left expression, and then all of the outputs produced by the right. For instance, filter .foo, .bar, produces both the "foo" fields and "bar" fields as separate outputs.

You can achieve the desired result either by using the * operator instead of the + operator for merging an object

echo "$json" | jq -r '. * {"a":{"b":"yy","c":{"e":"yy"}}}'

or by daisy-chaining updates of individual values (note the pipe between the two update operations):

echo "$json" | jq -r '. | .a.b |= "yy" | .a.c.e |= "yy"'
by (115)
2 18 33
edit history
...