How to generate all JSON paths conditionally with jq - json

I have arbitrarily nested JSON objects similar to below.
{
"parent1" : "someval"`,
"parent2" : {
"a" : "someval",
"b" : "someval"
},
"parent3" : {
"child1" : {
"a" : "someval"
},
"child2" : {
"b" : "someval"
}
}
}
I need to recursively go through them and check to see if any parent has children keys a or b, or both, and generate the JSON path to that parent like so:
Output:
parent2
parent3.child1
parent3.child2
I have tried using
jq -r 'path(..) | map (. | tostring) | join (".")
Which helps me generate all paths, but I haven't found a way to combine conditions like has("a") with path successfully. How can I go about achieving this ?

You could use index to check if you keys are in the path array:
path(..) | select(index("a") or index("b")) | join(".")
"parent2.a"
"parent2.b"
"parent3.child1.a"
"parent3.child2.b"
JqPlay demo
If you don't want the last key, you could add [:-1] to 'remove' the last index in each array to output:
path(..) | select(index("a") or index("b"))[:-1] | join(".")
"parent2"
"parent2"
"parent3.child1"
"parent3.child2"
JqPlay demo

Related

jq: How can I get array values based on superordinate key name

I'm trying to use jq to parse the output of https://ssl-config.mozilla.org/guidelines/5.6.json, a pretty simple JSON structure.
How can I get the "openssl" values if "configurations" is "modern" or "intermediate"?
The basic JSON structure would be:
{
"configurations": {
"intermediate": {
"ciphers": {
"openssl": [
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-CHACHA20-POLY1305",
"ECDHE-RSA-CHACHA20-POLY1305",
"DHE-RSA-AES128-GCM-SHA256",
"DHE-RSA-AES256-GCM-SHA384"
]
}
}
}
}
I had to shorten it in order to avoid the "It looks like your post is mostly code; please add some more detail" error message.
To get all both the modern and intermediate openssl arrays, we can use:
jq '.configurations | with_entries(select([.key] | inside([ "modern", "intermediate" ])))[] | .ciphers.openssl' input
This will show:
[]
[
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-CHACHA20-POLY1305",
"ECDHE-RSA-CHACHA20-POLY1305",
"DHE-RSA-AES128-GCM-SHA256",
"DHE-RSA-AES256-GCM-SHA384"
]
To get a result with an object so we can see on what key those openssl certs are found, use something like:
jq '.configurations | to_entries | map(select([.key] | inside([ "modern", "intermediate" ])) | { "\(.key)": .value.ciphers.openssl }) | add' input
This will produce:
{
"modern": [],
"intermediate": [
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-CHACHA20-POLY1305",
"ECDHE-RSA-CHACHA20-POLY1305",
"DHE-RSA-AES128-GCM-SHA256",
"DHE-RSA-AES256-GCM-SHA384"
]
}

How to access the value of an unknown key?

I have the following JSON file
{
"https://test.com/gg": [
"msg",
"popup",
"url"
]
}
What I want to achieve is to parse the values to output the following
https://test.com/gg?msg=gg
https://test.com/gg?popup=gg
https://test.com/gg?url=gg
I'm assuming it can be done using jq but I'm not sure how.
The way i know is if the elemets were named like bellow:
{
"url":"https://test.com/gg": [
"p1":"msg",
]
}
I would pull the elements like:
cat json | jq "url.[p1]"
But in my case is it not named.
jq --raw-output 'to_entries[0] | .key as $url | .value[] | "\($url)?\(.)=gg"' <your json file here>
Where
to_entries[0] yields {"key":"https://test.com/gg","value":["msg","popup","url"]}
(Save .key as $url for later)
Then "emit" all values with .value[]
For each "emitted" value, produce the string "\($url)?\(.)=gg" where . is the current value

"Ternary logic" for returned value: foo, bar or error

I've got two different JSON structures to retrieve a specific object value from, basically something like this
{
"one": {
"foo": {
"bar": "baz"
}
}
}
and another like that
{
"two": {
"foo": {
"bar": "qux"
}
}
}
I'd like to return the bar value in both cases plus an additional return variant error in case neither case 1 - baz - nor case 2 - qux - matches anything (i.e. matches null).
Is there a simple way to do that with just jq 1.6?
Update:
Here are snippets of actual JSON files:
/* manifest.json, variant A */
{
"browser_specific_settings": {
"gecko": {
"id": "{95ad7b39-5d3e-1029-7285-9455bcf665c0}",
"strict_min_version": "68.0"
}
}
}
/* manifest.json, variant B */
{
"applications": {
"gecko": {
"id": "j30D-3YFPUvj9u9izFoPSjlNYZfF22xS#foobar",
"strict_min_version": "53.0"
}
}
}
I need the id values (*gecko.id so to speak) or error if there is none:
{95ad7b39-5d3e-1029-7285-9455bcf665c0}
j30D-3YFPUvj9u9izFoPSjlNYZfF22xS#foobar
error
You can use a filter as below that could work with both your sample JSON content provided
jq '.. | if type == "object" and has("id") then .id else empty end'
See them live jqplay - VariantA and jqplay - VariantB
Note: This only gets the value of .id when it is present, see others answers (oguz ismail's) for displaying error when the object does not contain the required field.
(.. | objects | select(has("id"))).id // "error"
This will work with multiple files and files containing multiple separate entities.
jqplay demo
You can use a combination of the ? "error suppression" and // "alternative` operators :
jq -n --slurpfile variantA yourFirstFile --slurpfile variantB yourSecondFile \
'(
($variantA[0].browser_specific_settings.gecko.id)?,
($variantB[0].applications.gecko.id)?
) // "error"'
This will output the id from the first file and/or the id from the second file if any of the two exist, avoiding to raise errors when they don't, and output error instead if none of them can be found.
The command can be shortened as follows if it makes sense in your context :
jq -n --slurpfile variantA yourFirstFile --slurpfile variantB yourSecondFile \
'(($variantA[0].browser_specific_settings, $variantB[0].applications) | .gecko.id)? // "error"'
I think you are looking for hasOwnProperty()
for example:
var value;
if(applications.gecko.hasOwnProperty('id'))
value = applications.gecko.id;
else
value = 'error';
I need the id values (*gecko.id so to speak) or error if there is none:
In accordance with your notation "*gecko.id", here are two solutions, the first interpreting the "*" as a single unknown key (or index), the second interpreting it (more or less) as any number of keys or indices:
.[] | .gecko.id // "error"
.. | objects | select(has("gecko")) | (.gecko.id // "error")
If you don't really care about whether there's a "gecko" key, you might wish to consider:
first(.. | objects | select(has("id")).id ) // "error"

How to recurse with jq on nested JSON where each object has a name property?

I have a nested JSON object where each level has the same property key and what distinguishes each level is a property called name. If I want to traverse down to a level which has a particular "path" of name properties, how would I formulate the jq filter?
Here is some sample JSON data that represents a file system's directory structure:
{
"subs": [
{
"name": "aaa",
"subs": [
{
"name": "bbb",
"subs": [
{
"name": "ccc",
"subs": [
{
"name": "ddd",
"payload": "xyz"
}
]
}
]
}
]
}
]
}
What's a jq filter for obtaining the value of the payload in the "path" aaa/bbb/ccc/ddd?
Prior research:
jq - select objects with given key name - helpful but looks for any element in the JSON which contains the specified name whereas I'm looking for an element that's nested under a set of objects who also have specific names.
http://arjanvandergaag.nl/blog/wrestling-json-with-jq.html - helpful in section 4 where it shows how to extract an object having a property name having a particular value. However, the recursion performed is based a specific known set of property names ("values[].links.clone[]"). In my case, my equivalent is just "subs[].subs[].subs[]".
Here is the basis for a generic solution:
def descend(name): .subs[] | select(.name == name);
So your particular query could be formulated as follows:
descend( "aaa") | descend( "bbb") | descend( "ccc") | descend( "ddd") | .payload
Or slightly better, still using the above definition of descend:
def path(array):
if (array|length)==0 then .
else descend(array[0]) | path(array[1:])
end;
path( ["aaa", "bbb", "ccc", "ddd"] ) | .payload
TCO
The above recursive definition of path/1 is simple enough but would be unsuitable for very deeply nested data structures, e.g. if the depth is greater than 1000. Here is an alternative definition that takes advantage of jq's tail-call optimization, and that therefore runs very quickly:
def atpath(array):
[array, .]
| until( .[0] == []; .[0] as $a | .[1] | descend($a[0]) | [$a[1:], . ] )
| .[1];
.aaa.bbb.ccc.ddd
If you want to be able to use the .aaa.bbb.ccc.ddd notation, one approach would be to begin by "flattening" the data:
def flat:
{ (.name): (if .subs then (.subs[] | flat) else .payload end) };
Since the top-level element does not have a "name" tag, the query would then be:
.subs[] | flat | .aaa.bbb.ccc.ddd
Here is a more efficient approach, once again using descend defined above:
def payload(p):
def get($array):
if $array == []
then .payload
else descend($array[0]) | get($array[1:]) end;
get( null | path(p) );
payload( .aaa.bbb.ccc.ddd )
The filter in the following jq command recurses down a "path" of objects that have name properties which correspond to the "path" aaa/bbb/ccc/ddd:
jq '.subs[] | select(.name = "aaa") | .subs[] | select(.name = "bbb") | .subs[] | select(.name = "ccc") | .subs[] | .payload'
Here it is live on qplay.org:
https://jqplay.org/s/tblW7UX0Si

JQ: Filtering for keys

I'm trying to use the JQ command to filter a json object to selectively extract keys. Here's a sample object that I've placed in file x.txt:
{
"activities" : {
"-KSndgjqvmQkWVKHCpLh" : {
"create_device" : "...",
"stop_time_utc" : "2016-11-01T23:08:08Z"
},
"-KSoBSrBh6PZcjRocGD7" : {
"create_device" : "..."
},
"-KSptboGjo8g4bieUbGM" : {
"create_device" : "...",
"stop_time_utc" : "2017-01-17T23:08:08Z"
}
}
}
The following command can extract all of the activity keys:
cat x.txt | jq '.activities | keys'
[
"-KSndgjqvmQkWVKHCpLh",
"-KSoBSrBh6PZcjRocGD7",
"-KSptboGjo8g4bieUbGM"
]
I've been googling and experimenting for a few hours trying to filter the object to select only the activity entries that have a stop_time_utc value, and use something like a "select(.stop_time_utc | fromdateiso8601 > now)" to only pick activities that have expired. For example, I'd like to use filters to create an array from the sample object with only the one relevant entry:
[
"-KSndgjqvmQkWVKHCpLh"
]
Is attempting this with the keys option the wrong route? Any ideas or suggestions would be much appreciated.
with_entries/1 is your friend, e.g.:
.activities | with_entries( select(.value | has("stop_time_utc") ) )
produces:
{
"-KSndgjqvmQkWVKHCpLh": {
"create_device": "...",
"stop_time_utc": "2016-11-01T23:08:08Z"
},
"-KSptboGjo8g4bieUbGM": {
"create_device": "...",
"stop_time_utc": "2017-01-17T23:08:08Z"
}
It's now easy to add additional selection criteria, extract the key names of interest, etc. For example:
.activities
| with_entries( select( (.value.stop_time_utc? | fromdateiso8601?) < now ) )
| keys