Reputation: 2548
I'm working with this JSON (part of a larger JSON code):
group='{ "129": { "bounds": { "left": 20, "top": 20, "width": 250, "height": 200 }, "slot": 88, "userSize": null, "stackTabs": true, "showThumbs": true, "showUrls": true, "tileIcons": true, "catchOnce": true, "catchRules": "", "title": "", "id": 129 } }'
First I get the top-level key (here "129"
), which can actually be any number:
key=`echo $group | jq 'keys[0]'` # "129"
I want to change this key to a different number named nextID
which I got from another entry in the larger JSON:
nextID=`echo $groups_meta | jq '.nextID'` # 130
Now here is the problem. I am able to change the key with the following filter, but this seems to me rather fragile and complex:
new_group=`echo $group | jq --arg key $key --arg nextID $nextID 'with_entries( if .key | contains($key) then .key |= "$nextID" else . end)'`
You see that the filter finds the key by searching for the name. That may be a problem if there's another key later on with the same name. That's fragile. And this method seems rather complex; I'd really like to get the key (as in keys[0]
above) and use that for the change, all in the same filter.
So there's my question: is there a way to combine both parts, and get the key without searching?
UPDATE
Sample inputs:
group='{ "129": { "bounds": { "left": 20, "top": 20, "width": 250, "height": 200 }, "slot": 88, "userSize": null, "stackTabs": true, "showThumbs": true, "showUrls": true, "tileIcons": true, "catchOnce": true, "catchRules": "", "title": "", "id": 129 } }'
nextID=130
jq command:
new_group=`echo $group | jq --arg nexID $nextID 'filter'`
Expected output:
echo $new_group
{ "130": { "bounds": { "left": 20, "top": 20, "width": 250, "height": 200 }, "slot": 88, "userSize": null, "stackTabs": true, "showThumbs": true, "showUrls": true, "tileIcons": true, "catchOnce": true, "catchRules": "", "title": "", "id": 130 } }
Note that the first key "129"
should be addressed by position, similar to keys[0]
as shown above, as the number is not known beforehand. The value of key id
is also set to "130"
. Both should be set with the bash variable $nextID
imported into jq.
UPDATE 2
Using @jq170727's answer I added a few value changes after nextID
successfully, but there's an issue changing the .title value in the same input to a string with spaces:
title="new title"
new_group=`echo $group | jq --arg nextID $nextID --arg title $title '(keys_unsorted|first) as $i | with_entries(if .key == $i then .key=$nextID | .value.id=$nextID | .value.title=$title else . end)'`
This throws an error:
jq: error: title/0 is not defined at <top-level>, line 1:
title
Quoting the title variable in the jq command .value.title="$title"
also errors out. If title has no spaces in bash, the command works.
Am I missing a trick to update values with variables containing spaces? Should I ask a new question?
Upvotes: 0
Views: 1232
Reputation: 2548
Since the 'group' object has only one top-level key, there's no need to prevent sorting, so @jq170727's revised filter can be simplified quite a bit:
with_entries(.key=$nextID | .value.id=$nextID)
Answer to my update 2:
The imported bash variable needs to be quoted. To prevent errors due to spaces in a value it makes sense to quote all imported variables:
new_group=`echo $group | jq --arg nextID "$nextID" --arg title "$title" 'with_entries(.key=$nextID | .value.id=$nextID | .value.title=$title)'`
Output:
{ "130": { "bounds": { "left": 20, "top": 20, "width": 250, "height": 200 }, "slot": 88, "userSize": null, "stackTabs": true, "showThumbs": true, "showUrls": true, "tileIcons": true, "catchOnce": true, "catchRules": "", "title": "new title", "id": "130" } }
Upvotes: 0
Reputation: 14625
Your original filter was pretty close. Here is one that does what you requested.
(keys_unsorted|first) as $i | with_entries(if .key == $i then .key=$nexID| .value.id=$nexID else . end)
Interactive example within bash: Try it online!
Rather than use separate invocations you could do the entire operation at once.
For example assuming your complete json is something like
{
"group": {
"129": {
"bounds": { "left": 20, "top": 20, "width": 250, "height": 200 },
"slot": 88, "userSize": null, "stackTabs": true, "showThumbs": true, "showUrls": true,
"tileIcons": true, "catchOnce": true, "catchRules": "", "title": "", "id": 129
}
},
"groups_meta": {
"nextID": 130
}
}
You could use this filter
(.group | keys_unsorted | first) as $i
| (.groups_meta.nextID | tostring) as $n
| .group |= with_entries( if .key == $i then .key = $n | .value.id = $n else . end )
This trusts that the string generated from nextID
isn't in group
.
If you want to also update nextID
you can add something like | .groups_meta.nextID += 1
to the end.
Upvotes: 1