In the following json file,

{
  "email": "xxx",
  "pass": "yyy",
  "contact": [
    {
      "id": 111,
      "name": "AAA"
    }
  ],
  "lname": "YYY",
  "name": "AAA",
   "group": [
    {
      "name": "AAA",
      "lname": "YYY",
    }
  ],

I need to look for the key "name" and replace its value to "XXX" at all places. Which jq command does that ?

Using jq based on the walk function (needs a recent version):

jq 'walk(.name?="XXX")' file

If your jq doesn't support the walk function, just define it as:

jq '
  # Apply f to composite entities recursively, and to atoms
  def walk(f):
    . as $in
    | if type == "object" then
       reduce keys[] as $key
         ( {}; . + { ($key):  ($in[$key] | walk(f)) } ) | f
    elif type == "array" then map( walk(f) ) | f
    else f
    end;
  walk(.name?="XXX")
' file

Credits: https://github.com/stedolan/jq/issues/963

  • Hi I get the following error. error: Invalid character walk(.name?="XXX") ^ – user2181698 Oct 19 at 14:29
  • @user2181698 are you sure you're running that command on linux/unix? – qubert Oct 19 at 14:57
  • Perhaps @user2181698 is using an unusual shell and neglected to mention it? – Michael Hampton Oct 19 at 17:55

jq's assignment operations can perform an update on as many locations at once as you can name and are made for this sort of situation. You can use

jq '(.. | .name?) |= "XXXX"'

to find every field called "name" anywhere and replace the value in each all at once with "XXXX", and output the resulting object.

This is just the ..|.a? example from the recursive-descent documentation combined with update assignment.

It uses the recursive descent operator .. to find every single value in the tree, then pulls out the "name" field from each of them with .name, suppresses any errors from non-matching values with ?, and then updates the object in all those places at once with "XXXX" using the update-assignment operator |=, and outputs the new object.

This will work no matter what the file structure is and update every name field everywhere.


Alternatively, if the file always has this structure, and it's those particular "name" fields you want to change, not just any old name, you can also just list them out and assign to them as a group as well:

jq '(.name, .contact[].name, .group[].name) |= "XXXX"'

This does the same assignment to

  1. the "name" field of the top-level object;
  2. the "name" field of every object in the "contact" array; and
  3. the "name" field of every object in the "group" array.

all in one go. This is particularly useful if the file might have other name fields in there somewhere unrelated that you don't want to change. It finds just the three sets of locations named there and updates them all simultaneously.


If the value is just a literal like it is here then plain assignment with = works too and saves you a character: (..|.name?)="XXXX" - you'd also want this if your value is computed based on the whole top-level object. If instead you want to compute the new name based on the old one, you need to use |=. If I'm not sure what to use, |= generally has slightly nicer behaviour in the corner cases.

If you have multiple replacements to do, you can pipe them together:

jq '(..|.name?) = "XXXX" | (..|.lname?) = "1234"'

will update both the "name" and "lname" fields everywhere, and output the whole updated object once.


A few other approaches that may work:

  • You could also be really explicit about what you're selecting with

    (..|objects|select(has("name"))).name |= "XXXX"`
    

    which finds everything, then just the objects, then just the objects that have a "name", then the name field on those objects, and performs the same update as before.

  • If you're running the development version of jq (unlikely) then the walk function can also do the job: walk(.name?="XXXX"). All the other versions will work on the latest released version, 1.5.
  • An alternative multi-update could be

    jq '(..|has("name")?) += {name: "XXXX", lname: "1234"}'
    

    which finds everything with a name and then sets both "name" and "lname" on each object using arithmetic update-assignment *= and the merging behaviour that + has for objects.

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.