Search code examples
bashvariablesjq

Can jq parse more than one level key from variable?


I have a json file:

{
  "rec_9M":
    {
    "id":"000",
    "rec_E":
        [
          {
            "id":"111"
          } ,
          {
            "id":"222"
          }           
        ]
    }
}

I need to change values rec_9M.rec_E[].id but I need to use a universal bash function for that, which can be using for changing values on different levels (rec_9M.id or rec_9M.rec_E[].files[].id)

I tryed:

VAR=rec_9M.rec_E[]
NUM=555

echo $(cat config.json) | jq --arg VAR_I $VAR --arg NUM_I $NUM '.[$VAR_I].id = $NUM_I' > config_t.json
cat config_t.json 

Expect: enter image description here

{
  "rec_9M": {
    "id": "000",
    "rec_E": [
      {
        "id": "555"
      },
      {
        "id": "555"
      }
    ]
  }
}

Reality: enter image description here

{
  "rec_9M": {
    "id": "000",
    "rec_E": [
      {
        "id": "111"
      },
      {
        "id": "222"
      }
    ]
  },
  "rec_9M.rec_E[]": {
    "id": "555"
  }
}

If key was hardcoded - everything works correctly.:

echo $(cat config.json) | jq --arg VAR_I $VAR --arg NUM_I $NUM '.rec_9M.rec_E[].id = $NUM_I' > config_t.json

Also I tryed any combinations of using VAR_I in jq with quotes and special symbols, for example "($VAR_I)". Results are the same or jq parsing errors.

Maybe anybody know a solution?


Solution

  • One way uses getpath() to extract the paths of the input JSON based on a variable (Or other string):

    $ jq --arg path "rec_9M.rec_E" --arg i 555 'getpath($path | split("."))[] += {id: $i}' config.json
    {
      "rec_9M": {
        "id": "000",
        "rec_E": [
          {
            "id": "555"
          },
          {
            "id": "555"
          }
        ]
      }
    }
    

    Note leaving the trailing [] off the path; this only works with paths through object elements that end in an array of objects.

    Another, more flexible but also more fragile approach involves putting the path held in the shell variable directly in the jq program:

    $ path_var=".rec_9M.rec_E[].id"
    $ jq --arg i 555 "$path_var"' |= $i' config.json
    {
      "rec_9M": {
        "id": "000",
        "rec_E": [
          {
            "id": "555"
          },
          {
            "id": "555"
          }
        ]
      }
    }
    
    

    Note having to put a leading . in the path expression.


    echo $(cat config.json) has so many things wrong with it. jq takes the file names of JSON documents as arguments; use that. Or even a Useless Use of Cat like cat config.json | jq ... for example purposes is fine if you intend to replace the cat part with something more complex like a curl request or other command that outputs JSON... but don't combine it with echo. Besides introducing needless steps, if your shell's version of echo does things like replace \n with a literal newline, it's going to break everything else if escape sequences are present in strings in the JSON.