Tom Fenech
Tom Fenech

Reputation: 74615

Update JSON based on condition using jq

Supposing that I have a JSON file that looks like this:

{
  "name": "tom",
  "scripts": {
    "webpack": "webpack --progress --colors --watch"
  },
  "dependencies": {
  },
  "devDependencies": {
    "webpack": "^2.2.1"
  }
}

I would like to be able to specify a package by name, find the corresponding entry in either dependencies or devDependencies and update the value.

The closest I got was this:

$ jq --arg p webpack --arg v 1.2.3 'to_entries | map(
   if (.value[$p]? | startswith("^")?) then 
      .value[$p] = $v
   else . 
   end
) | from_entries' file.json

Which updates the value but also removes the dependencies and the name property:

{
  "scripts": {
    "webpack": "webpack --progress --colors --watch"
  },
  "devDependencies": {
    "webpack": "1.2.3"
  }
}

How can I update the desired value without affecting the other properties in the original JSON?

Upvotes: 2

Views: 1215

Answers (2)

peak
peak

Reputation: 116740

Using your approach, it would be better to use map_values:

map_values(if type == "object" 
           then with_entries( if .key == $p and (.value | startswith("^")?) 
                              then .value = $v else . end )
           else . end)

You might also like to consider using walk/1:

walk(if type == "object" and has($p) and (.[$p]|startswith("^"))
     then .[$p] = $v else . end)

The solution using walk/1 will examine the JSON entity recursively. If your jq does not have walk, its definition in jq can readily be found by googling.

Robust solution using when/2

def when(p;q): if p//false then q else . end;

map_values( when(type == "object";
                 with_entries( when( .key == $p and (.value | startswith("^")?);
                                     .value = $v) )) )

Upvotes: 1

Jeff Mercado
Jeff Mercado

Reputation: 134841

Try this instead:

$ jq --arg p webpack --arg v 1.2.3 '
def update_package($package; $version):
    if has($package) then .[$package] = $version
    else . end;
.dependencies |= update_package($p; $v)
    | .devDependencies |= update_package($p; $v)' project.json

update_package/2 will only update the dependency object if it actually references the given project, otherwise it is left alone. Apply to both dependencies and devDependencies.

The problem with your original filter that was removing the dependencies object was that since the project didn't exist, there was no corresponding value to update. By applying startswith on the nonexistent value, it caused an error. That error in turn was just ignored and effectively skipped the corresponding item in the map, thus losing that property.

For a quick fix to ensure this doesn't happen, don't ignore the error, provide an alternate value to use when the property doesn't exist.

.value[$p]?      | startswith("^")? # bad
.value[$p] // "" | startswith("^")  # better

Upvotes: 1

Related Questions