passing arguments to jq filter - json

Here is my config.json:
{
"env": "dev",
"dev": {
"projects" : {
"prj1": {
"dependencies": {},
"description": ""
}
}
}
}
Here are my bash commands:
PRJNAME='prj1'
echo $PRJNAME
jq --arg v "$PRJNAME" '.dev.projects."$v"' config.json
jq '.dev.projects.prj1' config.json
The output:
prj1
null
{
"dependencies": {},
"description": ""
}
So $PRJNAME is prj1, but the first invocation only outputs null.
Can someone help me?

The jq program .dev.projects."$v" in your example will literally try to find a key named "$v". Try the following instead:
jq --arg v "$PRJNAME" '.dev.projects[$v]' config.json

You can use --argjson too when you make your json.
--arg a v # set variable $a to value <v>;
--argjson a v # set variable $a to JSON value <v>;

As asked in a comment above there's a way to pass multiple argumets.
Maybe there's a more elegant way, but it works.
If you are sure always all keys needed you can use this:
jq --arg key1 $k1 --arg key2 $k2 --arg key3 $k3 --arg key4 $k4 '.[$key1] | .[$key2] | .[$key3] | .[$key4] '
If the key isn't always used you could do it like this:
jq --arg key $k ' if key != "" then .[$key] else . end'
If key sometimes refers to an array:
jq --arg key $k ' if type == "array" then .[$key |tonumber] else .[$key] end'
of course you can combine these!

you can do this:
key="dev.projects.prj1"
filter=".$key"
cat config.json | jq $filter

My example bash command to replace an array in a json file.
Everything is in variables:
a=/opt/terminal/conf/config.json ; \
b='["alchohol"]' ; \
c=terminal.canceledInspections.resultSendReasons ; \
cat $a | jq .$c ; \
cat $a | jq --argjson b $b --arg c $c 'getpath($c / ".") = $b' | sponge $a ; \
cat $a | jq .$c

Related

How to make a new array and add json value in bash

for region in $(jq '.data | keys | .[]' <<< "$data"); do
value=$(jq -r ".data[$region]" <<< "$data");
deliveryRegionId=$(jq -r '.deliveryRegionId' <<< "$value");
json_template='{}';
json_data=$(jq --argjson deliveryRegionId "$deliveryRegionId" --arg deliverableDistance 5000 '.deliveryRegionId=$deliveryRegionId | .deliverableDistance=5000' <<<"$json_template"); echo $json_data;
requestArray=$(jq '. += [$json_data]' <<< $requestArray)
done;
As in the code above, I'm going to create a json value called json_data and add it to the array.
What should I do to make this work?
jq: error: $json_data is not defined at <top-level>, line 1:
. += [$json_data]
jq: 1 compile error
this is error
There's no jq variable named $json_data.
There is a shell variable named that, but you can't access another program's variables.
Provide the value via the environment
json_data="$json_data" jq '. += [ env.json_data ]' <<<"$requestArray"
Provide the value via the environment
export json_data
jq '. += [ env.json_data ]' <<<"$requestArray"
Provide the value as an argument
jq --arg json_data "$json_data" '. += [ $json_data ]' <<<"$requestArray"
There's no reason to use jq so many times! Your entire program can be replaced with this:
requestArray="$(
jq '.data | map( { deliveryRegionId, deliverableDistance: 5000 } )' \
<<<"$data"
)"
Demo on jqplay

Getting empty string after parsing bash associative array to json using jq

I have a bash associative array containing dynamic data like:
declare -A assoc_array=([cluster_name]="cpod1" [site_name]="ppod1" [alarm_name]="alarm1")
I have to create the JSON data accordingly.
{
"name": "cluster_name",
"value": $assoc_array[cluster_name],
"regex": False
}
{
"name": "site_name",
"value": $assoc_array[site_name],
"regex": False
}
{
"name": "alert_name",
"value": $assoc_array[alert_name],
"regex": False
}
I have used the following code for it:
for i in "${!assoc_array[#]}"; do
echo $i
alarmjsonarray=$(echo ${alarmjsonarray}| jq --arg name "$i" \
--arg value "${alarm_param[$i]}" \
--arg isRegex "False" \
'. + [{("name"):$name,("value"):$value,("isRegex"):$isRegex}]')
done
echo "alarmjsonarray" $alarmjsonarray
I am getting empty string from it. Can you please help me in it?
Assuming the ASCII record separator character \x1e can't occur in your data (and given your assertions that newlines will never be present), one way to handle this would be:
for key in "${!assoc_array[#]}"; do
printf '%s\x1e%s\n' "$key" "${assoc_array[$key]}"
done | jq -Rn '
[
inputs |
split("\u001e") | .[0] as $key | .[1] as $value |
{"name": $key, "value": $value, "regex": false}
]'
...feel free to change \x1e to a different character (like a tab) that can never exist, as appropriate.
#!/bin/bash
declare -A assoc_array=([cluster_name]="cpod1" [site_name]="ppod1" [alarm_name]="alarm1")
for i in "${!assoc_array[#]}"; do
alarmjsonarray=$(echo "${alarmjsonarray:-[]}" |
jq --arg name "$i" \
--arg value "${assoc_array[$i]}" \
--arg isRegex "False" \
'. + [{("name"):$name,("value"):$value,("isRegex"):$isRegex}]')
done
echo "alarmjsonarray" "$alarmjsonarray"
array was supposed to be initialized before using it. I thought in bash, scope is there out of block also.
Its more simpler way to generate json from bash associative array.

How to conditionally select array index to update based on value

I have a json file as below, need to append the new name into .root.application.names, but if the name passed in has prefix (everything before -, in below example it's jr), then find the list with same prefix names already present, and update it, if there is only one names list or if there is no matching list, then update first list.
In the below example, if
$application == 'application1' and $name == <whatever>; just update first list under application1, as there is only one list under application1, nothing to choose from.
$application == 'application2' and if $name has no prefix delimiter "-" or unmatched prefix (say sr-allen); then update the first list under application2.names, because foo has no or unmatched prefix.
$application == 'application2' and say $name == jr-allen; then update the second list under application2, because $name has prefix "jr-" and there is a list with items matching this prefix.
{
"root": {
"application1": [
{
"names": [
"john"
],
"project": "generic"
}
],
"application2": [
{
"names": [
"peter",
"jack"
],
"project": "generic"
},
{
"names": [
"jr-sam",
"jr-mike",
"jr-rita"
],
"project": "junior-project"
}
]
}
}
I found how to update the list, not sure how to add these conditions, any help please?
jq '."root"."application2"[1].names[."root"."application2"[1].names| length] |= . + "jr-allen"' foo.json
Update:
good if I can do this with jq/walk, I am still trying as below, but couldn't get anywhere close.
prefix=$(echo ${name} | cut -d"-" -f1) # this gives the prefix, e.g: "jr"
jq -r --arg app "${application}" name "${name}" prefix "${prefix}"'
def walk(f):
. as $in
| if type == "object" then
reduce keys[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
walk( if type=="object" and ."$app" and (.names[]|startswith("$prefix")) ) then .names[]="$name" else . end )
' foo.json
It took me a while to have any confidence that I have understood the requirements, but I believe the following at least captures the essence of what you have in mind.
To make the solution easier to understand, we begin with a helper function, the name of which makes its purpose clear enough, at least given the context:
def updateFirstNamesArrayWithMatchingPrefix($prefix; $value):
(first( range(0; length) as $i
| if any(.[$i].names[]; startswith($prefix))
then $i else empty end) // 0) as $i
| .[$i].names += [$value] ;
.root |=
if .[$app] | length == 1
then .[$app][0].names += [ $name ]
elif .[$app] | length > 1
then
( $name | split("-")) as $components
| if $components|length==1 # no prefix
then .[$app][0].names += [ $name ]
else ($components[0] + "-" ) as $prefix
| .[$app] |= updateFirstNamesArrayWithMatchingPrefix($prefix; $name)
end
else .
end
Testing
The above passes the four tests originally proposed by
#Inian:
jq --arg app "application1" --arg name "foo" -f script.jq jsonFile
jq --arg app "application2" --arg name "jr-foo" -f script.jq jsonFile
jq --arg app "application2" --arg name "sr-foo" -f script.jq jsonFile
jq --arg app "application2" --arg name "foo" -f script.jq jsonFile
Herrings?
Based on my understanding of the problem, it seems to me that walk may be a bit of a red herring, but if not, I hope you'll be able to adapt the above to meet your actual requirements.
Your requirement is: If prefix exists in name, update the last array element, else the first one.
When the array has one element, like for application1, the last is the first also.
#!/bin/bash
application="$1"
name="$2"
json_file="file.json"
ind=0
# if name matches "-", set index to the last array element
[[ "$name" == *"-"* ]] && ind=-1
jq --arg app "$application" \
--arg name "$name" \
--argjson ind "$ind" \
'.root[$app][$ind].names += [$name]' "$json_file"
I believe above script is self-explanatory enough, --argjson used for having an unquoted index, += stands for |= . +.
Testing
Commands below produce the expected result.
bash test.sh application1 jr-John
bash test.sh application2 jr-John
bash test.sh application1 Mary
bash test.sh application2 Mary

Send bash array to json file

I pulled some config variables from a json file with jq.
And once modified, i want to write the whole config array (which can contain keys that were not there at first) to the json file.
The "foreach" part seems quite obvious.
But how to express "change keyA by value A or add keyA=>valueA" to the conf file ?
I'm stuck with something like
for key in "${!conf[#]}"; do
value=${conf[$key]}
echo $key $value
jq --arg key $key --arg value $value '.$key = $value' $conf_file > $new_file
done
Thanks
Extended solution with single jq invocation:
Sample conf array:
declare -A conf=([status]="finished" [nextTo]="2018-01-24" [result]=true)
Sample file conf.json:
{
"status": "running",
"minFrom": "2018-01-23",
"maxTo": "2018-01-24",
"nextTo": "2018-01-23",
"nextFrom": "2018-01-22"
}
Processing:
jq --arg data "$(paste -d':' <(printf "%s\n" "${!conf[#]}") <(printf "%s\n" "${conf[#]}"))" \
'. as $conf | map($data | split("\n")[]
| split(":") | {(.[0]) : .[1]})
| add | $conf + .' conf.json > conf.tmp && mv conf.tmp conf.json
The resulting conf.json contents:
{
"status": "finished",
"minFrom": "2018-01-23",
"maxTo": "2018-01-24",
"nextTo": "2018-01-24",
"nextFrom": "2018-01-22",
"result": "true"
}
I finally run into the following solution : not as compact as RomanPerekhrest, but a bit more human-readable. Thanks.
function update_conf() {
echo update_conf
if [ -n "$1" ] && [ -n "$2" ]; then
conf[$1]=$2
fi
for key in "${!conf[#]}"; do
value=${conf[$key]}
jq --arg key "$key" --arg value "$value" '.[$key] = $value' $confFile > $confTempFile && mv $confTempFile $confF$
done
}

Modify Nested JSON with jq

I'm trying to modify nested JSON objects using the jq <map> function in a bash/shell script; something similar to this blog entry but attempting to adapt the examples here to nested objects.
The returned JSON to be modified as follows:
{
"name": "vendor-module",
"dependencies": {
"abc": {
"from": "abc#2.4.0",
"resolved": "https://some.special.url",
"version": "2.4.0"
},
"acme": {
"from": "acme#1.2.3",
"resolved": "<CHANGE_THIS>",
"version": "1.2.3"
}
}
}
This would be my attempt:
modules="`node -pe 'JSON.parse(process.argv[1]).dependencies.$dependency' \
"$(cat $wrapped)"`"
version="1.2.3"
resolved="some_url"
cat OLD.json |
jq 'to_entries |
map(if .dependencies[0].$module[0].from == "$module#$version"
then . + {"resolved"}={"$resolved"}
else .
end
) |
from_entries' > NEW.json
Obviously this doesn't work. When I run the script the NEW.json is created but without modifications or returned errors. If I don't target a nested object (e.g., "name": "vendor-module"), The script works as expected. I am sure there is a way to do it using native bash and jq..?? Any help (with the proper escaping) will be greatly appreciated.
UPDATE:
Thnx from the help of Charles Duffy's answer, and his suggestion of using sponge, The solution that works well for me is:
jq --arg mod "acme" --arg resolved "Some URL" \
'.dependencies[$mod].resolved |= $resolved' \
OLD.json | sponge OLD.json
If you know the name of the dependency you want to update, you could just index into it.
$ jq --arg dep "$dep" --arg resolved "$resolved" \
'.dependencies[$dep].resolved = $resolved' \
OLD.json > NEW.json
Otherwise, to modify a dependency based on the name (or other property), search for the dependency and update.
$ jq --arg version "$version" --arg resolved "$resolved" \
'(.dependencies[] | select(.version == $version)).resolved = $resolved' \
OLD.json > NEW.json
For your existing sample data, the following suffices:
jq --arg mod "acme" \
--arg resolved "some_url" \
'.dependencies[$mod].resolved=$resolved' \
<in.json >out.json
...to filter on the from, by contrast:
jq --arg new_url "http://new.url/" \
--arg target "acme#1.2.3" \
'.dependencies=(.dependencies
| to_entries
| map(if(.value.from == $target)
then .value.resolved=$new_url
else . end)
| from_entries)' \
<in.json >out.json