jsonjq

Using JQ query how to update propery inside two nested arrays?


I tried this for 2 days but can't figure it out in any way.

This is my JSON:

{
    "items": [
        {
            "name":"itemA",
            "bins":[
                {
                    "bin_id":"b1",
                    "count":"11"
                },
                {
                    "bin_id":"b2",
                    "count":"22"
                }
            ]
        },
        {
            "name":"itemB",
            "bins":[
                {
                    "bin_id":"b5",
                    "count":"55"
                }
            ]
        }
    ]
}

Important note. Input JSON could be empty object '{}' or missing any part of the hierarchy.

I need to insert whole hierary or update final entry for "count". My input arguments are: itemName, binId and new count.

So for "itemA", "b1" and count 68. I need to set .items -> (item with name: "itemA") -> (bin with bin_id 68) -> count = 68.

I tried using INDEX to access items. And that works... but I can't seem to further access bins and update final count.

UPDATE:

I got something like this:

.items |= [INDEX(.[]?;.name) | .itemA |= .+ {name:"itemA",bins:[INDEX(.bins[]?;.bin_id) | .b1 |= .+ {bin_id:"b1",count:"188000"} | .[]]} | .[]]

But it does look ugly. Can this be done more clearly?


Solution

  • Given these arguments, this is the adapted, now nested approach from the previous question:

    jq --arg name itemA --arg bin_id b1 --arg count 68 '
      .items |= [INDEX(.[]?; .name) | .[$name] |= ({$name, bins}
        | .bins |= [INDEX(.[]?; .bin_id) | .[$bin_id] |= {$bin_id, $count} | .[]]
      ) | .[]]
    '
    

    Demo

    This can be generalized by moving the repeating code into a function, and nesting the calls:

    jq --arg name itemA --arg bin_id b1 --arg count 68 '
      def f(a;b;c): .[a] |= [INDEX(.[]?; .[b|keys[]]) | .[b[]] |= b+c | .[]];
      f("items"; {$name}; f("bins"; {$bin_id}; {$count}))
    '
    

    Demo

    Finally, making the function recursive allows for an arbitrarily deep nesting. Note the modified way providing the arguments as (container_field index_field index_value)* field value, which now directly includes all the field names:

    jq 'def f(v): .[v[0]] |= if v | length < 3 then v[1] else
      [INDEX(.[]?; .[v[1]]) | .[v[2]] |= {(v[1]): v[2]} + f(v[3:]) | .[]]
    end; f($ARGS.positional)' --args items name itemA bins bin_id b1 count 68
    

    Demo

    For an empty input, all of them produce:

    {
      "items": [
        {
          "name": "itemA",
          "bins": [
            {
              "bin_id": "b1",
              "count": "68"
            }
          ]
        }
      ]
    }
    

    And for the sample input, they output:

    {
      "items": [
        {
          "name": "itemA",
          "bins": [
            {
              "bin_id": "b1",
              "count": "68"
            },
            {
              "bin_id": "b2",
              "count": "22"
            }
          ]
        },
        {
          "name": "itemB",
          "bins": [
            {
              "bin_id": "b5",
              "count": "55"
            }
          ]
        }
      ]
    }