196

Appologies if I've overlooked something very obvious; I've just found jq and am trying to use it to update one JSON value without affecting the surrounding data.

I'd like to pipe a curl result into jq, update a value, and pipe the updated JSON to a curl -X PUT. Something like

curl http://example.com/shipping.json | jq '.' field: value | curl -X PUT http://example.com/shipping.json

So far I've hacked it together using sed, but after looking at a few examples of the |= operator in jq I'm sure that I don't need these.

Here's a JSON sample--how would I use jq to set "local": false, while preserving the rest of the JSON?

{
  "shipping": {
    "local": true,
    "us": true,
    "us_rate": {
      "amount": "0.00",
      "currency": "USD",
      "symbol": "$"
    }
  }
}
STW
  • 44,917
  • 17
  • 105
  • 161

3 Answers3

197

You set values of an object using the = operator. |= on the other hand is used to update a value. It's a subtle but important difference. The context of the filters changes.

Since you are setting a property to a constant value, use the = operator.

.shipping.local = false

Just note that when setting a value to a property, it doesn't necessarily have to exist. You can add new values easily this way.

.shipping.local = false | .shipping.canada = false | .shipping.mexico = true
Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
  • 17
    This sample is not good for normal value. So if you need change the normal value, you need add `"` with it, such as `.shipping.local = "new place"`. So the whole command will be `curl http://example.com/shipping.json | jq '.shipping.local = "new place"'`. Otherwise, you will get strange errors. – BMW Jul 27 '18 at 01:39
  • 11
    @BMW what? It's perfectly fine here. Any valid json value is valid, this just happens to be the literal `false`. Values do not have to be strings. – Jeff Mercado Jul 27 '18 at 02:07
  • 1
    @BMW the OP wants to set it with `false`. What's wrong? – SOFe Jan 13 '19 at 12:28
  • 1
    How would you set `.shipping.` where VAR is defined in jq? For a simple example, how would you modify `jq --arg location local '.shipping.$location = false'` to make it work? – Max Murphy Oct 08 '20 at 09:14
  • 4
    @MaxMurphy: `.shipping[$location] = false` – Jeff Mercado Oct 08 '20 at 16:56
  • = sets, |= updates? In other words = can't add properties to an object? – x-yuri Jun 09 '21 at 16:48
  • @x-yuri: You absolutely can, the property you would set would be on the LHS. The only thing that changes between `=` and `|=` is what `.` is in the RHS. – Jeff Mercado Jun 09 '21 at 17:23
  • Works for simple cases like in OP but breakdowns in anything more complex, e.g. set property `value` to `a` where property `key` on the object is `b`. – Andrew Savinykh Jun 18 '21 at 02:27
  • Imagine .shipping was nested 3 levels down and the parent keys were indeterminate so we could not match on them from the top. But we want to update the `.shipping.us_rate.amount` where `.shipping.local=true` and `.shipping.us=true`. How do we match and update the json, returning the whole document otherwise unchanged except for the updates? – Ed Randall Aug 09 '23 at 15:21
  • 1
    @EdRandall Using this approach, you'd just have to search for that object you want to update and make the assignments. Just need to know how to identify what objects to update. But I'd imagine you could do: `(.. | objects | select(has("shipping")).shipping | select(.local == true and .us == true).us_rate.amount) = "blah"` (note the parentheses) – Jeff Mercado Aug 09 '23 at 17:09
111

Update a value (sets .foo.bar to "new value"):

jq '.foo.bar = "new value"' file.json

Update a value using a variable (sets .foo.bar to "hello"):

variable="hello"; jq --arg variable "$variable" '.foo.bar = $variable' file.json
Paul Chris Jones
  • 2,646
  • 1
  • 23
  • 21
  • 32
    This doesn't actually replace the contents of the file. It only prints to stdout. You will need to save stout back to the file to make the change persist. See https://stackoverflow.com/a/60744617/1626687 – spuder Jun 12 '20 at 21:17
  • This is great, thanks! Can you do an example for updating two properties at the same time, pretty please? – ndtreviv Mar 23 '22 at 10:26
  • 5
    For those wondering, you can literally pipe it: `jq '.foo.bar = "new value" | .foo.baz = "other value"' file.json` – ndtreviv Mar 23 '22 at 14:09
13

a similar function to the operator |= is map. map will be suitable to avoid the requirement of a previous filter for the array...

imagine that your data is an array (very common for this example)

[
  {
    "shipping": {
      "local": true,
      "us": true,
      "us_rate": {
        "amount": "1.00",
        "currency": "USD",
        "symbol": "$"
      }
    }
  },
  {
    "shipping": {
      "local": true,
      "us": true,
      "us_rate": {
        "amount": "1.00",
        "currency": "USD",
        "symbol": "$"
      }
    }
  }
]

hence it is necessary to consider the array in the code as:

http://example.com/shipping.json | jq '.[] | .shipping.local = "new place"' | curl -X PUT http://example.com/shipping.json

or to use the map function that is crafted to work in every array element as

http://example.com/shipping.json | jq 'map(.shipping.local = "new place")' | curl -X PUT http://example.com/shipping.json

Observation

For the sake of those that are learning, you also did some mistakes in the jq usage, just consider that it does "read" the 1st parameter as the program, hence all the desired commands shall be included in the very first string after calling the program.

Thiago Conrado
  • 726
  • 8
  • 15
  • Why is this necessarily different to `.[].shipping.local = "new place"`? – roaima Oct 15 '20 at 14:47
  • 1
    the difference in this case, is the response, using the map function will give an array of objects, while to other option, you will a sequence of objects, one per line (useful to integrate to other interpreters as bash) – Thiago Conrado Oct 15 '20 at 17:26