jq - Selecting objects containing certain key - json

Let's say I have this JSON file below:
{
"team": {
"money": 100,
},
"group": {
"money": 200,
"snack": true,
}
}
I want to select the objects which has a "snack" key including its parent. The current command I'm using is:
jq '..|objects|select(has("snack"))' json
This however, does not include the parent, which in this case is "group". How do I select the parent of the selected object as well?

Instead of using .., you could use paths. That is, you'd select the paths that lead to the items of interest, and work from there. So you'd start with:
paths(objects) as $p
| select(getpath($p)|has("snack"))
| $p
For the given input (after having been corrected), this would yield:
["group"]
So you might want to replace the $p in the last line by $p[-1], but it's not altogether clear how useful that would be. More useful would be getpath( $p[:-1] )

Related

Filtering JSON using jq with a condition

I have the following JSON:
{
"LaunchTemplates": [
{
"LaunchTemplateName": "bla-99",
"CreateTime": "2022-12-13T13:40:33+00:00"
},
{
"LaunchTemplateName": "abcabc",
"CreateTime": "2022-12-13T09:58:14+00:00"
},
{
"LaunchTemplateName": "bla-34",
"CreateTime": "2022-12-13T13:58:56+00:00"
},
{
"LaunchTemplateName": "bla-222",
"CreateTime": "2022-12-11T13:58:56+00:00"
},
{
"LaunchTemplateName": "bla-233",
"CreateTime": "2022-12-10T13:58:56+00:00"
}
]
}
I want to filter the JSON and print the oldest templates after filtering. I have the following jq query that prints the template names after filtering:
file.json | jq '.LaunchTemplates[].LaunchTemplateName|select(startswith("bla"))'
Output:
bla-99
bla-34
bla-222
bla-233
Now i want to add more logic to the query, and do something like that: If the number of bla lines is bigger than 3, then print the oldest bla lines (according to the date field). In my case, the output should be:
bla-233
Is that possible with jq or other shell commands? If so, how?
If you are only interested in the last two elements (and ignoring the fact that the input in the question is invalid JSON):
.LaunchTemplates
| sort_by(.CreateTime)
| map(.LaunchTemplateName|select(startswith("")))[:-3]
| reverse[]
sort_by(.CreateTime) sorts ascending by the CreateTime property.
map(.LaunchTemplateName|select(startswith("bla")) maps the input array to an array containing only the template names with a "bla" prefix.
[:-3] slices the input array to drop the last 3 elements (i.e. dropping the 3 newest elements.
reverse[] reverses the array and streams its elements.
Output:
"bla-222"
"bla-233"

How to pull keys and values in jq csv when you don't know the key name

I have a semi working jq filter that pulls out .key, .value.sitename and shows it in a one line csv format. I'd like to add any tag_* keys and values (if found) at the end of the csv line.
[.key , .value.hostname, .value.attributes.sitename ] | #csv
I need help with the .value.attributes.tag* keys since they might be completely missing or different tag_ names. They all start with tag_ but could be anything. I'd like to pair the found tag name and value together if possible and append it on the csv line with the host.
{
"key": "computer1.domain.com",
"value": {
"attributes": {
"TESTID": "23423423",
"sitename": "siteidname",
"tag_robo_equip": "boopbeep",
"tag_modern": "cybertruck"
},
"hostname": "computer1.domain.com",
}
}
At the end of the line "hostname": "computer1.domain.com", the comma is an error.
If you correct this, the solution could be:
Proposal
At the you have 2 additional columns one with the keys an other with the values in your csv file.

Add a new entry to a Json after or before a specified existing entry with jq

I have the following input and I want to below output using jq.
I would like to add
an entry "zeiterfassungAktiviert" : truejust after the key gueltigBis (or alternatively before the key inhaltsverzeichnis)
add an entry to the end of the object inhaltsverzeichnis by adding the entry "zeiterfassung": "zeiterfassung"
Example input:
{
"fachbereich": "qp",
"produktTyp": "PRODUKT_ANFRAGE_V1",
"name": "Produkt Anfrage",
"kurzName": "anfrage",
"gueltigAb": "2019-01-01T00:00:00.000",
"gueltigBis": "2022-12-31T00:00:00.000",
"inhaltsverzeichnis": {
"versandumfang": "auftragsverwaltung/versandumfang",
"dokumentenerzeugung": "dokumentenerzeugung"
}
}
Example output:
{
"fachbereich": "qp",
"produktTyp": "PRODUKT_ANFRAGE_V1",
"name": "Produkt Anfrage",
"kurzName": "anfrage",
"gueltigAb": "2019-01-01T00:00:00.000",
"gueltigBis": "2022-12-31T00:00:00.000",
"zeiterfassungAktiviert": true,
"inhaltsverzeichnis": {
"versandumfang": "auftragsverwaltung/versandumfang",
"dokumentenerzeugung": "dokumentenerzeugung",
"zeiterfassung": "zeiterfassung"
},
}
I managed to do the second part but am not clear about how to go about the first part.
Command: jq '.zeiterfassungAktiviert += "zeiterfassung" | .inhaltsverzeichnis.zeiterfassung += "zeiterfassung"'
The result is as follows:
{
"fachbereich": "qp",
"produktTyp": "PRODUKT_ANFRAGE_V1",
"name": "Produkt Anfrage",
"kurzName": "anfrage",
"gueltigAb": "2019-01-01T00:00:00.000",
"gueltigBis": "2022-12-31T00:00:00.000",
"inhaltsverzeichnis": {
"versandumfang": "auftragsverwaltung/versandumfang",
"dokumentenerzeugung": "dokumentenerzeugung",
"zeiterfassung": "zeiterfassung"
},
"zeiterfassungAktiviert": true
}
As you can see it is added to the end of the root object. I would like to specify the position somehow, ideally without having to convert into array and convert back if possible but rather by saying please add entry after/before a specified key.
For the first part, it helps to have a helper function:
def insertkv($afterkey; $key; $value):
def insertafter($ix; $x): .[0:1+$ix] + [$x] + .[1+$ix:];
to_entries
| insertafter( map(.key) | index($afterkey); {$key, $value})
| from_entries;
Versions
The above is intended for use with jq 1.5 or later. Some minor fiddling is required for earlier versions.

jq - How do I print a parent value of an object when I am already deep into the object's children?

Say I have the following JSON, stored in my variable jsonVariable.
{
"id": 1,
"details": {
"username": "jamesbrown",
"name": "James Brown"
}
}
I parse this JSON with jq using the following:
echo $jsonVariable | jq '.details.name | select(.name == "James Brown")'
This would give me the output
James Brown
But what if I want to get the id of this person as well? Now, I'm aware this is a rough and simple example - the program I'm working with at the moment is 5 or 6 levels deep with many different JQ functions other than select. I need a way to select a parent's field when I am already 5 or 6 layers deep after carrying out various methods of filtering.
Can anyone help? Is there any way of 'going in reverse', back up to the parent? (Not sure if I'm making sense!)
For a more generic approach, save the value of the "parent" element at the detail level you want, then pipe it at the end of your filter:
jq '. as $parent | .details.name | select(. == "James Brown") | $parent'
Of course, for the trivial case you expose, you could omit this entirely:
jq 'select(.details.name == "James Brown")'
Also, consider that if your selecting filters return many matches for a single parent object, you will receive a copy of the parent object for each match. You may wish to make sure your select filters only return one element at the parent level by wrapping all matches below parent level into an array, or to deduplicate the final result with unique.
Give this a shot:
echo $jsonVariable | jq '{Name: .details.name, Id: .Id} | select(.name == "James Brown")'
Rather than querying up to the value you're testing for, query up to the root object that contains the value you're querying on and the values you wish to select.
You need the object that contains both the id and the name.
$ jq --arg name 'James Brown' 'select(.details.name == $name).id' input.json

How to print outer JSON attribute on each line with the inner ones?

I have JSON like this:
{
"files": [
{ "size": 3227238, "name": "file1.mp3" },
{ "size": 3737995, "name": "file2.mp3" }
],
"id": "music"
}
And I want to print each file on its own line, but including the id from the outer element.
file1.mp3 music
file2.mp3 music
I can get the file attributes like this:
jq -r '.files | .[] | .name'
But how do I include the id on each line?
You could use this filter:
"\(.files[].name) \(.id)"
This will yield a string for each of the files containing the name and the outer id.
You will probably want to get the raw output so don't forget the -r flag.
If you wanted to pull more than just the name from each of the files, you would need to make adjustments to the filter. The key is to use .[] at most, once within any one part of the filter. Depending on where you want each part in the string, there are a number of approaches you can take.
If they are all together in one part of your string, you can just add another string combining the properties you want:
"\(.files[] | "\(.name) \(.size)") \(.id)"
Otherwise, it would be easier to introduce a variable:
.id as $id | .files[] | "\(.name) \($id) \(.size)"