OrangePot
OrangePot

Reputation: 1082

jq: Change multiple values

I'm trying to change multiple json values with this line

 jq '.two="newval", .three="newval"' my.json 

with this is the input

{
  "one": {
    "val": 1
  },
  "two": "val",
  "three": "val",
  "four": "val"
}

but the output is 2 jsons:

{
  "one": {
    "val": 1
  },
  "two": "newval",
  "three": "val",
  "four": "val"
}
{
  "one": {
    "val": 1
  },
  "two": "val",
  "three": "newval",
  "four": "val"
}

How can I change multiple values and output in one item?

Upvotes: 31

Views: 24257

Answers (3)

ZachP
ZachP

Reputation: 701

This old question is popular on Google and I had a similar need that led to a more concise solution (which I'm not sure if existed at the time of ask). In my case, I needed to assign the same value to multiple keys which fits the example OP gave but it bears warning that this won't work for assigning different values.

Solving OP's problem

Keep the comma operator and pass the combined stream to the assignment:

$ jq '(.two, .three) = "newval"' data.json
{
  "one": {
    "val": 1
  },
  "two": "newval",
  "three": "newval",
  "four": "val"
}

jqPlay example: https://jqplay.org/s/Jx7m-pnl_OQ

My extended problem


I needed to mask sensitive (secret) values in Kubernetes manifests that looked like this:

{
  "apiVersion": "v1",
  "kind": "Secret",
  "metadata": {
    "name": "test"
  },
  "data": {
    "foo": "czAwcGVyczNrcml0Cg=="
  },
  "stringData": {
    "baz": "s00pers3krit"
  }
}

With a few additional complicating factors:

  1. The foo and baz sub-keys were arbitrary and unknown ahead of time
  2. One or both of the data and stringData keys could be present
  3. There may be multiple documents in the input stream and only .kind == "Secret" should be masked

My solution


$ helm template helm/ | yq '( select(.kind == "Secret")  | (.data[]?, .stringData[]?) ) = "[Masked]"'
{
  "apiVersion": "v1",
  "kind": "Secret",
  "metadata": {
    "name": "secret1"
  },
  "type": "kubernetes.io/dockerconfigjson",
  "data": {
    ".dockerconfigjson": "[Masked]"
  },
  "stringData": {
    ".dockerconfigjson": "[Masked]"
  }
}
{
  "apiVersion": "v1",
  "kind": "Secret",
  "metadata": {
    "name": "secret2"
  },
  "type": "kubernetes.io/dockerconfigjson",
  "data": {
    "baz": "[Masked]"
  },
  "stringData": {
    "quux`": "[Masked]"
  }
}
{
  "apiVersion": "v1",
  "kind": "ConfigMap",
  "metadata": {
    "name": "configmap1"
  },
  "type": "kubernetes.io/dockerconfigjson",
  "data": {
    "foo": "czAwcGVyczNrcml0Cg==",
    "snuffle": "fuzzy"
  }
}
  1. Use a subselect to assign the static value [Masked] to the selected keys:
    () = "[Masked]"
    
  2. Select only .kind == "Secret" for reassignment:
    ( select(.kind == "Secret") ) = "[Masked]"
    
  3. Pipe the selected objects in a stream generator to produce multiple fields to be assigned:
    ( select(.kind == "Secret") | (., .) ) = "[Masked]"
    
  4. But only the data and stringData of those objects:
    ( select(.kind == "Secret") | (.data, .stringData) ) = "[Masked]"
    
  5. And really only the subkeys of those (keep .data but modify all its child key):
    ( select(.kind == "Secret") | (.data[], .stringData[]) ) = "[Masked]"
    
  6. And finally only if those sub keys actually exist:
    ( select(.kind == "Secret") | (.data[]?, .stringData[]?) ) = "[Masked]"
    

jqPlay: https://jqplay.org/s/8SqKgeP1BzN

Upvotes: 4

peak
peak

Reputation: 116750

Simply change the comma to a pipe character and you’re done:

.two="newval" | .three="newval"

"," is for concatenating streams: A,B will emit A followed by B.

Upvotes: 71

jq170727
jq170727

Reputation: 14665

Here is a method which uses + object addition to update multiple members.

. + {two:"newtwo", three:"newthree"}

Sample Run (assumes data in data.json)

$ jq -M '. + {two:"newtwo", three:"newthree"}' data.json
{
  "one": {
    "val": 1
  },
  "two": "newtwo",
  "three": "newthree",
  "four": "val"
}

Try it online at jqplay.org

Upvotes: 16

Related Questions