Search code examples
jsoneditjqany

modify nested JSON array with JQ


I would like to use JQ to modify the following JSON input:

{
  "rows": [
    {
      "fields": [
        {
          "name": "id",
          "value": "k1"
        },
        {
          "name": "val",
          "value": "2340378b211aa3d8f2d7607cbddce883b87b191d0425736641e3d308ea329718"
        },
        {
          "name": "Encoding",
          "value": "hex"
        }
      ]
    },
    {
      "fields": [
        {
          "name": "id",
          "value": "k2"
        },
        {
          "name": "val",
          "value": "2340378b211aa3d8f2d7607cbddce883b87b191d0425736641e3d308ea329718"
        },
        {
          "name": "Encoding",
          "value": "hex"
        }
      ]
    }
  ]
}

so that the "hex" value of the "value" field with name "Encoding" is modified to "oct" only for the row that contains a field with .name=="id" and .value=="k1".

How would I do that? I can select a field with .value=="k1" etc. but I don't seem to be able to find a way to "go up the tree" to update the "Encoding" field subsequently.

The input cannot be expected to have a fixed field/row order.


Solution

  • Using any/2 yields a jq filter that closely matches the description of the problem in English:

    .rows |= map( if any(.fields[]; .name=="id" and .value=="k1")
                  then .fields |= map(if .name == "Encoding"
                                      then .value = "oct"
                                      else .
                                      end)
                  else .
                  end )
    

    Using when/2

    A slightly less verbose and perhaps clearer solution is possible if we use a handy general-purpose function defined as follows:

    def when(filter; action): if (filter?) // null then action else . end;
    

    We can then write simply:

    .rows[] |= when( any(.fields[]; .name=="id" and .value=="k1");
                     .fields |= map( when (.name == "Encoding";
                                           .value = "oct")))
    

    Caveats

    • You might want to check whether .value == "hex" before changing it to "oct".
    • The above filters could potentially change more than one name/value pair per "row".