Updating JSON: |=, +=, del and in-place edits

jq · Advanced · 10 min

Editing config files, bumping versions and patching API payloads require changing a field without rebuilding the whole document.

Most jq filters extract. Assignment operators instead return the whole input with something changed.

  • .version = "2.0" sets a field (creating it if missing). The right side is evaluated against the original input, so .b = .a copies .a into .b.
  • .price |= . * 1.1 updates: the right side runs with the current value at that path as input. This is the one you want most of the time.
  • Arithmetic shortcuts: .count += 1, -=, *=, /=, and //= which sets a default only if the value is null or false.

The left side can be any path expression, including iterators and select:

  • .items[].price |= . * 0.9 discounts every item.
  • (.items[] | select(.sku == "A1") | .qty) += 5 updates only matching items.
  • .users |= map(select(.active)) filters a nested list in place.

del(path) removes; to_entries-style rewrites are rarely needed.

jq never edits files in place. Write to a temporary file and move it: jq '.version = "2.0"' package.json > tmp && mv tmp package.json (or sponge from moreutils). Redirecting to the same file you read (> package.json) truncates it before jq reads it, leaving it empty.

Example

echo '{"version":"1.0","items":[{"sku":"A1","qty":1,"price":10},{"sku":"B2","qty":3,"price":20}]}' > o.json
jq -c '.version = "2.0" | .version' o.json                     # "2.0"
jq -c '.items[].price |= . * 0.5 | [.items[].price]' o.json    # [5,10]
jq -c '(.items[] | select(.sku == "A1") | .qty) += 5 | .items[0].qty' o.json   # 6
jq -c '.items |= map(select(.qty > 1)) | .items | length' o.json   # 1
jq -c '.owner //= "nobody" | .owner' o.json                     # "nobody"
jq -c 'del(.items[] | select(.sku == "B2")) | .items' o.json
# [{"sku":"A1","qty":1,"price":10}]
jq '.version = "2.0"' o.json > o.tmp && mv o.tmp o.json        # safe "in-place" edit

Remember

  • |= updates with the value at the path as input; = uses the whole input
  • Left side can use .[] and select to update many places
  • Write to a temp file then mv; never > the same file

Watch out for

  • Running jq ... file.json > file.json, which empties the file before jq reads it.

Check yourself

  1. Input `{"a":1,"b":5}`. What is `.a |= . + 1`?
  2. Input `{"a":1,"b":5}`. What is `.a = .b`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: jqlang.org/manual/