Converting JSON response to key value pair using jq - bash

So, I am getting a response from an API that I am calling in a shell script in the following form
[{"id":100000004,"name":"Customs Clearance Requested"},{"id":100000005,"name":"Customs Cleared"},{"id":100000006,"name":"Cargo Loaded to Vessel"}]
I want to create a map out of it that will help me lookup the id's from a name and use it in the shell script. So something like map["Customs Clearance Requested"] would give me 100000004 which I can use further. Can this be done using jq? I am pretty new to shell scripting and jq and got stuck with above thing

json='[{"id":100000004,"name":"Customs Clearance Requested"},{"id":100000005,"name":"Customs Cleared"},{"id":100000006,"name":"Cargo Loaded to Vessel"}]'
declare -A map
while IFS= read -r -d '' name && IFS= read -r -d '' value; do
map[$name]=$value
done < <(jq -j '.[] | "\(.name)\u0000\(.id)\u0000"' <<<"$json")
declare -p map # demo purposes: print the map we created as output
...emits as output:
declare -A map=(["Cargo Loaded to Vessel"]="100000006" ["Customs Clearance Requested"]="100000004" ["Customs Cleared"]="100000005" )
...which you can query exactly as requested:
$ echo "${map['Cargo Loaded to Vessel']}"
100000006

You could use the select function, e.g.:
data='[{"id":100000004,"name":"Customs Clearance Requested"},{"id":100000005,"name":"Customs Cleared"},{"id":100000006,"name":"Cargo Loaded to Vessel"}]'
jq 'map(select(.["name"] == "Customs Clearance Requested"))' <<< $data
It will get all elements which name equals "Customs Clearance Requested", e.g.:
[
{
"id": 100000004,
"name": "Customs Clearance Requested"
}
]
If you want to get the id field:
jq 'map(select(.["name"] == "Customs Clearance Requested")["id"])' <<< $data
This will output:
[
100000004
]
Please note that it will return an array and not a single element because the search does not know how many results will be found.
If you want to generalize this in a shell function, you could write:
function get_id_from_name
{
# $1=name to search for
local filter=$(printf 'map(select(.["name"] == "%s")["id"])' "$1")
jq "$filter"
}
Then call it like that:
get_id_from_name "Customs Clearance Requested" <<< $data
If your data is stored in a file, you could call it this way:
get_id_from_name "Customs Clearance Requested" < /path/to/file.json

The following is very similar to #CharlesDuffy's excellent answer but does not assume that the .name and .id values are NUL-free (i.e., do not have any "\u0000" characters):
declare -A map
while read -r name
do
name=$(sed -e 's/^"//' -e 's/"$//' <<< "$name")
read -r id
map[$name]="$id"
done < <(echo "$json" | jq -c '.[]|.name,.id')
The point is that the -j option is like -r (i.e., produces "raw output"), whereas the -c option produces JSON.
This means that if you don't want the .id values as JSON strings, then the above won't be a solution; also, if the .name values contain double-quotes, then you might want to deal with the occurrences of \".

Related

jq How to pass key starting with numeral as argument [duplicate]

I am new to jq and facing an issue while parsing my json
I have a json stored in a variable like this
temp='{ "1": { "my_name": "one" }, "2": { "my_name": "two" } }'
Now I need to get the value of my_name for both other entries
I have tried something like this
echo $temp | jq '.1' //out put 0.1
I was assuming to get { "my_name": "one" }
And similarly to get my_name value I did
echo $temp | jq '.1.my_name' // Its output is giving me error
Can any one please help to identify what is wrong with my syntax and how can I correct it.
Just the number gets interpreted as a float. You need to use it in a context where it is unambiguously a key string.
echo "$temp" | jq '.["1"]["my_name"]'
and
echo "$temp" | jq '.["1"]'
to get the containing dict.
With a sufficiently new jq (I think >= 1.4) you can also say
echo "$temp" | jq '."1".my_name'
Whenever you are trying to reference a key that is not a valid identifier, you have to quote it. See the manual for more details.
To select the item under the key 1, you'd do this:
."1"
For your other question on how to obtain the my_name values, you could do this:
to_entries | map(.value.my_name)

Iterate over JSON data for each specific key

I have a JSON with this format:
{
"instance-01": {
"instance_aws_az": "eu-west-1a",
"instance_aws_dns_name": "<ip>eu-west-1.compute.amazonaws.com",
"instance_ec2_tags": {
"Name": "2ed97a79c27be6bca883",
"Serial_number": "22000528",
},
"instance_sg_groups": [],
},
"instance-29": {
"instance_aws_az": "eu-west-1a",
"instance_aws_dns_name": "<ip>eu-west-1.compute.amazonaws.com",
"instance_ec2_tags": {
"Name": "2ed97a79c27be6bca773",
"Serial_number": "22000578",
},
"instance_sg_groups": [],
}
}
So, the main keys are "instance-01" and "instance-29". I can get that info with:
jq -c 'keys[]' instances_metadata.json
However, I want to use the key names I get with that to iterate over the nested info for each one, for example:
jq -c 'keys[]' instances_metadata.json | while read i; do
echo "Instance $i: Serial Number is: $i.instance_ec2_tags.Serial_number"
// Edit with more specific tasks I want to run
if [ "instance-01" = "$i" ]; then
echo "Instance $i: "
// Something like that to filter by key.. I tried several options but with no success
serial_number=$(jq -r '.[].instance_ec2_tags.Serial_number | select(. == "instance-01")' instances_metadata.json )
echo "Value: $serial_number"
aws s3 cp .... //using $serial_number and other values
fi
done
How can I do this? I just found questions to iterate but ignoring which key name it has and in this case I need to have that info inside the loop.
If the task is accomplishable within jq, there's no need for a bash loop and multiple jq calls, as you can do the iteration inside jq using variables with the as notation as in keys[] as $i | …. For example:
jq -r '
keys[] as $i
| "Instance \($i): Serial Number is: \(.[$i].instance_ec2_tags.Serial_number)"
'
Instance instance-01: Serial Number is: 22000528
Instance instance-29: Serial Number is: 22000578
Demo
If it is not, then you will have to loop in bash. Introduce the loop variable to the nested jq calls using --arg and reference the according object using it as .[$varname]. Here's your edited example with the modifications mentioned:
jq -r 'keys[]' instances_metadata.json | while read i; do
echo "Instance $i: Serial Number is: $(jq -r --arg i "$i" '.[$i].instance_ec2_tags.Serial_number' instances_metadata.json)"
// Edit with more specific tasks I want to run
if [ "instance-01" = "$i" ]; then
echo "Instance $i: "
// Something like that to filter by key.. I tried several options but with no success
serial_number="$(jq -r --arg i "$i" '.[$i].instance_ec2_tags.Serial_number' instances_metadata.json)"
echo "Value: $serial_number"
aws s3 cp .... //using $serial_number and other values
fi
done

bash loop error : Get JSON Object by property with jq / bash

I would like to get the values from Json file. Which is working.
JsonFileToTest:
{
"permissions": [
{
"emailid": "test1#test.com",
"rights": "read"
},
{
"emailid": "test2#test.com",
"rights": "read"
}
]
}
readPermissions=($(jq -r '.permissions' JsonFileToTest))
# The command below works perfectly, But when I Put it in a loop, It does not.
#echo ${readPermissions[#]} | jq 'values[].emailid'
for vals in ${readPermissions[#]}
do
# I would like o extract the email id of the user. The loop is not working atm.
echo ${vals[#]} | jq 'values[].emailid'
done
what am I missing here?
thanks
If you really want to do it this way, that might look like:
readarray -t permissions < <(jq -c '.permissions[]' JsonFileToTest)
for permissionSet in "${permissions[#]}"; do
jq -r '.emailid' <<<"$permissionSet"
done
Note that we're telling jq to print one line per item (with -c), and using readarray -t to read each line into an array element (unlike the array=( $(...command...) ) antipattern, which splits not just on newlines but on other whitespace as well, and expands globs in the process).
But there's no reason whatsoever to do any of that. You'll get the exact same result simply running:
jq -r '.permissions[].emailid' JsonFileToTest

JQ get key based on variable value

I'm trying to create a ohmyzsh function for Salesforce's DX CLI based on Wade Wegner's guide here. In order to get the value I want I need to expand how he is using JQ which I've never heard of before. I get the premise for this use case but I'm struggling with one abstraction point (within the aliasConfig json). Here's my script so far
get_sfdx_defaultusername() {
config="$(cat .sfdx/sfdx-config.json 2> /dev/null)";
globalConfig="$(cat ~/.sfdx/sfdx-config.json)";
aliasConfig="$(cat ~/.sfdx/alias.json)";
defaultusername="$(echo ${config} | jq -r .defaultusername)"
defaultusernamealias="NEED HELP HERE"
globaldefaultusername="$(echo ${globalConfig} | jq -r .defaultusername)"
if [ ! $defaultusernamealias = "null" ]
then
echoString=$echoString$defaultusernamealias"$txtylw (alias)"
elif [ ! $defaultusername = "null" ]
then
echoString=$echoString$defaultusername"$txtylw (local)"
else
echoString=$echoString$globaldefaultusername"$txtylw (global)"
fi
echo $echoString"\n"
}
The alias.json looks like this:
{
"orgs": {
"HubOrg": "myemail#domain.com",
"my-scrath-org": "test-jdj1iflkor4k#mydomain.net"
}
}
Using the ${defaultusername} I know the value in this case to be "test-jdj1iflkor4k#mydomain.net", therefore I need it to set the value of defaultusernamealias to "my-scrath-org"
NOTE: The closest answer I found was this, but unfortunately I still couldn't get what I needed with it.
Congratulations on figuring out how to use to_entries.
One small suggestion is to avoid using shell interpolation to "construct" the jq program. A much better way to achieve the desired goal is to pass in the relevant values on the command-line. In your case, the following would be appropriate:
$ jq --arg username "$defaultusername" '
.orgs | to_entries[] | select(.value == $username ).key'
Another small point is to avoid using echo to send JSON to STDIN. There are several possibilities, including these patterns:
if you are using bash: jq .... <<< "$JSON"
use printf "%s" "$JSON" | jq ...
jq -n --argjson JSON "$JSON" '$JSON | ...'
In your case, the last of these alternatives would look like this:
$ jq --arg username "$defaultusername" --argjson JSON "$aliasConfig" '
$JSON
| .orgs | to_entries[] | select(.value == $username ).key'
I think I got it figured out here:
get_sfdx_defaultusername() {
config="$(cat .sfdx/sfdx-config.json 2> /dev/null)";
globalConfig="$(cat ~/.sfdx/sfdx-config.json)";
aliasConfig="$(cat ~/.sfdx/alias.json)";
defaultusername="$(echo ${config} | jq -r .defaultusername)"
defaultusernamealias="$(echo ${aliasConfig} | jq -r '.orgs | to_entries[] | select(.value =="'$defaultusername'").key' )"
globaldefaultusername="$(echo ${globalConfig} | jq -r .defaultusername)"
if [ ! $defaultusernamealias = "null" ]
then
echoString=$echoString$defaultusernamealias"$txtylw (alias)"
elif [ ! $defaultusername = "null" ]
then
echoString=$echoString$defaultusername"$txtylw (local)"
else
echoString=$echoString$globaldefaultusername"$txtylw (global)"
fi
echo $echoString"\n"
}
This allows me to show my current defaultusername org like so:
In case anyone is interested in using this or contributing to it, I published a github repo here

using jq to assign multiple output variables

I am trying to use jq to parse information from the TVDB api. I need to pull a couple of fields and assign the values to variables that I can continue to use in my bash script. I know I can easily assign the output to one variable through bash with variable="$(command)" but I need the output to produce multiple variables and I don't want to make to use multiple commands.
I read this documentation:
https://stedolan.github.io/jq/manual/v1.5/#Advancedfeatures
but I don't know if this relevant to what I am trying to do.
jq '.data' produces the following output:
[
{
"absoluteNumber": 51,
"airedEpisodeNumber": 6,
"airedSeason": 4,
"airedSeasonID": 680431,
"dvdEpisodeNumber": 6,
"dvdSeason": 4,
"episodeName": "We Will Rise",
"firstAired": "2017-03-15",
"id": 5939660,
"language": {
"episodeName": "en",
"overview": "en"
},
"lastUpdated": 1490769062,
"overview": "Clarke and Roan must work together in hostile territory in order to deliver an invaluable asset to Abby and her team."
}
]
I tried jq '.data | {episodeName:$name}' and jq '.data | .episodeName as $name' just to try and get one working. I don't understand the documentation or even if it's what I'm looking for. Is there a way to do what I am trying to do?
You can use separate variables with read :
read var1 var2 var3 < <(echo $(curl -s 'https://api.github.com/repos/torvalds/linux' |
jq -r '.id, .name, .full_name'))
echo "id : $var1"
echo "name : $var2"
echo "full_name : $var3"
Using array :
read -a arr < <(echo $(curl -s 'https://api.github.com/repos/torvalds/linux' |
jq -r '.id, .name, .full_name'))
echo "id : ${arr[0]}"
echo "name : ${arr[1]}"
echo "full_name : ${arr[2]}"
Also you can split jq output with some character :
IFS='|' read var1 var2 var3 var4 < <(curl '......' | jq -r '.data |
map([.absoluteNumber, .airedEpisodeNumber, .episodeName, .overview] |
join("|")) | join("\n")')
Or use an array like :
set -f; IFS='|' data=($(curl '......' | jq -r '.data |
map([.absoluteNumber, .airedEpisodeNumber, .episodeName, .overview] |
join("|")) | join("\n")')); set +f
absoluteNumber, airedEpisodeNumber, episodeName & overview are respectively ${data[0]}, ${data[1]}, ${data[2]}, ${data[3]}. set -f and set +f are used to respectively disable & enable globbing.
For the jq part, all your required fields are mapped and delimited with a '|' character with join("|")
If your are using jq < 1.5, you'll have to convert Number to String with tostring for each Number fields eg:
IFS='|' read var1 var2 var3 var4 < <(curl '......' | jq -r '.data |
map([.absoluteNumber|tostring, .airedEpisodeNumber|tostring, .episodeName, .overview] |
join("|")) | join("\n")')
jq always produces a stream of zero or more values. For example, to produce the two values corresponding to "episodeName" and "id"' you could write:
.data[] | ( .episodeName, .id )
For your purposes, it might be helpful to use the -c command-line option, to ensure each JSON output value is presented on a single line. You might also want to use the -r command-line option, which removes the outermost quotation marks from each output value that is a JSON string.
For further variations, please see the jq FAQ https://github.com/stedolan/jq/wiki/FAQ, e.g. the question:
Q: How can a stream of JSON texts produced by jq be converted into a bash array of corresponding values?
Experimental conversion of quoted OP input, (tv.dat), to a series of bash variables, (and an array). The jq code is mostly borrowed from here and there, but I don't know how to get jq to unroll an array within an array, so the sed code does that, (that's only good for one level, but so are bash arrays):
jq -r ".[] | to_entries | map(\"DAT_\(.key) \(.value|tostring)\") | .[]" tv.dat |
while read a b ; do echo "${a,,}='$b'" ; done |
sed -e '/{.*}/s/"\([^"]*\)":/[\1]=/g;y/{},/() /' -e "s/='(/=(/;s/)'$/)/"
Output:
dat_absolutenumber='51'
dat_airedepisodenumber='6'
dat_airedseason='4'
dat_airedseasonid='680431'
dat_dvdepisodenumber='6'
dat_dvdseason='4'
dat_episodename='We Will Rise'
dat_firstaired='2017-03-15'
dat_id='5939660'
dat_language=([episodeName]="en" [overview]="en")
dat_lastupdated='1490769062'
dat_overview='Clarke and Roan must work together in hostile territory in order to deliver an invaluable asset to Abby and her team.'

Resources