Narmada
Narmada

Reputation: 1

Adding new JSON fields using jq when value not matched

I have below sample json

{
  "environment": [
        {
      "name": "user1",
      "value": "app"
    },
            {
      "name": "user2",
      "value": "admin"
    },
    {
      "name": "user3",
      "value": "db"
    }


  ]
}

I need to update the value with new value (passed as an input argument to the script) when name exists. If different name and value are given, need to append them as new fields in the end. For example, if name=user4 and value=root then my new json should look like this

{
  "environment": [
        {
      "name": "user1",
      "value": "app"
    },
            {
      "name": "user2",
      "value": "admin"
    },
    {
      "name": "user3",
      "value": "db"
    },
    {
      "name": "user4",
      "value": "root"
    }

  ]
}

If existing values given as user3, aws then it has to update the value of user3 as follows

{
  "environment": [
        {
      "name": "user1",
      "value": "app"
    },
            {
      "name": "user2",
      "value": "admin"
    },
    {
      "name": "user3",
      "value": "aws"
    }


  ]
}

Its working as expected but only has the below problem. When my JSON has any extra tags before environment tag, after execution of command, those are getting trimmed off and next time when I run same command with different inputs, It won't find the previous existing tag and giving this error Cannot iterate over null (null) With the below command that is given by peak, taskDefinition & containerDefinitions tags are getting trimmed off. Hence please help me to have these tags in place.

 jq  --arg updateName "user5" --arg updateVal "admin" '
  .taskDefinition | .containerDefinitions[] | .environment |=
       if any(.[]; .name == $updateName)
       then map(if .name == $updateName then .value = $updateVal else . end)
       else (. + [{"name": $updateName, "value": $updateVal}] )
       end' envt_values1.json
{
    "taskDefinition": {
        "containerDefinitions": [
            {
              "environment": [
                {
                  "name": "user1",
                  "value": "app"
                },
                {
                  "name": "user2",
                  "value": "admin"
                },
                {
                  "name": "user3",
                  "value": "aws"
                },
                {
                  "name": "user4",
                  "value": "root"
                },
                {}
              ],
              "configuration": []
            }
        ]
    }
}

Upvotes: 0

Views: 395

Answers (2)

pmf
pmf

Reputation: 36033

Do you care about the order of the elements in the array to be modified?

No, the array is unordered

Take an array of only those elements that do not match (map(select(.name != $name))) and unconditionally add an element with the new data to it (+ [{$name,$value}]). That way, an existing element gets deleted if and only if it matches, and a new one is added in any case. The one added (or updated) will always be at the array's final position, not at any 'deleted position'.

jq --arg name "user4" --arg value "root"  '
  
  .environment |= map(select(.name != $name)) + [{$name,$value}]

' envt.json

Yes, the array is ordered

If the array is ordered, only a truly new element should be added at the end of the array, while updated elements should be replaced at their original positions within the array.

For this, we don't update the whole array as before (.environment |= …), just certain elements that matter ((.environment | …) |= …). If there are elements matching the condition ((.[] | select(.name == $name))), they matter, otherwise (//) the element at position length (past the end of the array, thus new, as indices go from 0 to only length-1) is the one that matters. Whichever elements get through, they will be updated with the data provided (|= {$name,$value}):

jq --arg name "user4" --arg value "root"  '
  
  (.environment | ((.[] | select(.name == $name)) // .[length])) |= {$name,$value}

' envt.json

Sidenote: I also renamed the input variables to $name and $value (according to the future field names) in order to simplify creating the new object as {$name,$value}.

Upvotes: 0

peak
peak

Reputation: 116680

This implements the requirements as I understand them:

jq  --arg updateName "user4" --arg updateVal "root" '
  .environment |= 
       if any(.[]; .name == $updateName)
       then map(if .name == $updateName then .value = $updateVal else . end)
       else (. + [{"name": $updateName, "value": $updateVal}] )
       end' input.json
       

Upvotes: 1

Related Questions