2

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?

2 Answers 2

1

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
Sign up to request clarification or add additional context in comments.

4 Comments

That's great, thanks. Is there a way to call the function in a loop? I was trying to adapt your code to use foreach [.dependencies, .devDependencies] but couldn't get it to work.
You could do this: .["dependencies","devDependencies"] |= update_package($p; $v)
or even (.dependencies,.devDependencies) |= update_package($p; $v)
I decided to "inline" the function in the end (.dependencies,.devDependencies) |= if has($p) then .[$p] = $v else . end.
1

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) )) )

4 Comments

Sorry, I'm afraid my example was over-simplified - there are other properties in the file which cause this approach to fail. I've edited my question to add a property with a string value.
The answer using map_values has been modified in accordance with the modified question; the answer using walk does not need to be modified, though of course it can be made more robust.
I think I was over-complicating things by trying to traverse the object when really I knew that the change needed to be made in one of two places. Where does the convention of adding /N to the name of functions come from?
The name/arity notation has been used for a long time in Prolog and Erlang, and more recently in Elixir and no doubt others.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.