Extract child elements and add parent field into them - json

I am trying to combine nested arrays into a single object so that I can do some sorting. For example, I have the following
{
"disk": [
{
"device": "/dev/sda",
"partitions": [
{ "type": "fat32", "mount": "/efi" },
{ "type": "ext4", "mount": "/boot" }
]
},
{
"device": "/dev/sdb",
"partitions": [
{ "type": "xfs", "mount": "/" }
]
}
]
}
I am trying to say 'give me all partitions where mount is not null, sort them by mount, but include their device name in the output'.
So far I have jq -c '.disk[].partitions[] | select (.mount != null)' which is giving me the correct partitions as such:
{ "type": "xfs", "mount": "/" }
{ "type": "ext4", "mount": "/boot" }
{ "type": "fat32", "mount": "/efi" }
However, I would like to pull in the parent device as such:
{ "type": "xfs", "mount": "/", "device": "/sdb" }
{ "type": "ext4", "mount": "/boot", "device": "/sda" }
{ "type": "fat32", "mount": "/efi", "device": "/sda" }
I've seen other examples that drive off the parent and then pull in the children, but it doesn't seem to work when the parent itself is an array. Is there a way to say "get a child property" such as ... | .device = ..device

You don't need to go back one level to fetch device. Just get a copy of it, select partitions, and add them together.
.disk
| map((.partitions[] | select(.mount != null)) + {device})
| sort_by(.mount)[]
Online demo

You can enrich the partitions with the device and filter the enriched partitions:
jq '.disk[]
| .device as $d
| .partitions[] += { device: $d }
| .partitions[]
| select(.mount != null)
' file.json

Related

How to search within sections of a JSON File?S

So, lets say I had a JSON File like this:
{
"content": [
{
"word": "cat",
"adjectives": [
{
"type": "textile",
"adjective": "fluffy"
},
{
"type": "visual",
"adjective": "small"
}
]
},
{
"word": "dog",
"adjectives": [
{
"type": "textile",
"adjective": "fluffy"
},
{
"type": "visual",
"adjective": "big"
}
]
},
{
"word": "chocolate",
"adjectives": [
{
"type": "visual",
"adjective": "small"
},
{
"type": "gustatory",
"adjective": "sweet"
}
]
}
]
}
Now, say I wanted to search for two words. For example, "Fluffy" and "Small." The problem with this is that both words' adjectives contain small, and so I would have to manually search for which one contains fluffy. So, how would I do this in a quicker manner?
In other words, how would I find the word(s) with both "fluffy" and "small"
EDIT: Sorry, new asker. Anything that words in a terminal is fair game. jq is a really great JSON searcher, and so this is preferred, and sorry for the confusion. I also fixed the JSON
A command-line solution would be to use jq:
jq -r '.content[] | select(.adjectives[].adjective == "fluffy") | .word' /pathToJsonFile.json
Output:
cat
Are you looking for something like this? Do you need a solution that uses other programming languages?
(P.S. your JSON example appears to be invalid)
Since jq is now fair game (this was only clarified later in the comments), here is one solution using jq.
First, fix the JSON to be actually valid:
{
"content": [
{
"word": "cat",
"adjectives": [
{
"type": "textile",
"adjective": "fluffy"
},
{
"type": "visual",
"adjective": "small"
}
]
},
{
"word": "dog",
"adjectives": [
{
"type": "textile",
"adjective": "fluffy"
},
{
"type": "visual",
"adjective": "big"
}
]
},
{
"word": "chocolate",
"adjectives": [
{
"type": "visual",
"adjective": "small"
},
{
"type": "gustatory",
"adjective": "sweet"
}
]
}
]
}
Then, the following jq filter returns an array containing the words which contain both adjectives:
.content
| map(
select(
.adjectives as $adj
| all("small","fluffy"; IN($adj[].adjective))
)
| .word
)
If a non-array output is required, and only one word per line, use .[] instead of map (either after content or as a final filter), e.g.:
jq -r '.content[]
| select(
.adjectives as $adj
| all("small","fluffy"; IN($adj[].adjective))
)
| .word'

iterate on key to get the contents of the key

IF I have json that looks like
{
"items": [
{
"name": "issue1",
"spec": {
"config": {
"setting1": abc,
"setting2": {
"name": "xyz"
}
},
"files": {
"name": "cde",
"path": "/home"
},
"program": {
"name": "apache"
}
}
}
]
}
and I want to have iteration at .items[0].spec where the key config,files,program's contents can be shown.. something like
config:
{
"setting1": abc,
"setting2": {
"name": "xyz"
}
}
files:
{
"name": "cde",
"path": "/home"
}
program:
{
"name": "apache"
}
and there might be things more or less than config/files/programs for each items.
I know that I can get the list of keys by jq -r '.items[0].spec| to_entries[].key' but not sure about formating the output and getting the contents of .items[].spec.xxxxx like above.
I also got very close by jq -r '{test: .items[0].spec | with_entries(select(.value != null)) }' but I want to seperate each item per key
Also this got me even closer! but how do you get the json syntax of the object to display uncompressed?
jq -r '.items[].spec | keys[] as $k | "\($k):\n \(.[$k])"'
The "," operator is the magic ingredient you seem to be looking for:
.items[].spec | keys[] as $k | $k, .[$k]

jq: insert{"key": "value"} pair in the nested structure

I just wonder how we can achieve this with simple online jq.
AS_IS
{
"rules": {
"aaa": {
"xxx": {
"url": "https://microsoft.com"
},
"xxxx": {
"url": "https://netflix.com"
}
},
"bbb": {
"xxx": {
"url": "https://amazon.com"
}
},
"ccc": {
"xxx": {
"url": "https://google.com"
}
}
}
}
TO_BE
{
"rules": {
"aaa": {
"xxx": {
"url": "https://microsoft.com"
},
"xxxx": {
"url": "https://netflix.com"
}
},
"bbb": {
"xxx": {
"url": "https://amazon.com"
}
},
"ccc": {
"xxx": {
"url": "https://google.com",
"pass": "abc"
}
}
}
}
What I tried so far is
.rules[] | select(.xxx.url | try contains("google.com")) | .xxx += {"pass": "abc"}
and the output is below, and I successfully insert "pass": "abc".
{
"xxx": {
"url": "https://google.com",
"pass": "abc"
}
}
but I want to get the whole lines of code. (Not a part of it)
ref. https://jqplay.org/s/S_UWrrLRl5
The idea is right, but ensure the last update happens without traversing down to the leaf path (at the same level as xxx), but relative to the root path by simply wrapping the filter under (..)
( .rules[] | select(.xxx.url | try contains("google.com")) | .xxx ) += {"pass": "abc"}
Always prefer using exact match conditions instead of a partial match
( .rules[] | select(.xxx.url == "https://google.com") | .xxx ) += {"pass": "abc"}
Here's an even more "dynamic" approach:
walk(if type=="object"
and (.url|type)=="string"
and (.url|endswith("google.com"))
then .pass = "abc" else . end)
Of course, depending on the task requirements, a simpler solution such as the following might suffice:
.rules.ccc.xxx.pass = "abc"

Modifying array of key value in JSON jq

In case, I have an original json look like the following:
{
"taskDefinition": {
"containerDefinitions": [
{
"name": "web",
"image": "my-image",
"environment": [
{
"name": "DB_HOST",
"value": "localhost"
},
{
"name": "DB_USERNAME",
"value": "user"
}
]
}
]
}
}
And I would like to inplace modify the value for the matched key like so:
jq '.taskDefinition.containerDefinitions[0].environment[] | select(.name=="DB_USERNAME") | .value="new"' json
I got the output
{
"name": "DB_USERNAME",
"value": "new"
}
But I want more like in-place modify or the whole json from the original with new value modified, like this:
{
"taskDefinition": {
"containerDefinitions": [
{
"name": "web",
"image": "my-image",
"environment": [
{
"name": "DB_HOST",
"value": "localhost"
},
{
"name": "DB_USERNAME",
"value": "new"
}
]
}
]
}
}
Is it possible to do with jq or any known workaround?
Thank you.
Updated
For anyone looking for editing multi-values,
here is the approach I use
JQ=""
for e in DB_HOST=rds DB_USERNAME=xxx; do
k=${e%=*}
v=${e##*=}
JQ+="(.taskDefinition.containerDefinitions[0].environment[] | select(.name==\"$k\") | .value) |= \"$v\" | "
done
jq '${JQ%??}' json
I think there should be more concise way, but this seems working fine.
It is enough to assign to the path, if you are using |=, e.g.
jq '
(.taskDefinition.containerDefinitions[0].environment[] |
select(.name=="DB_USERNAME") | .value) |= "new"
' infile.json
Output:
{
"taskDefinition": {
"containerDefinitions": [
{
"name": "web",
"image": "my-image",
"environment": [
{
"name": "DB_HOST",
"value": "localhost"
},
{
"name": "DB_USERNAME",
"value": "new"
}
]
}
]
}
}
Here is a select-free solution using |=:
.taskDefinition.containerDefinitions[0].environment |=
map(if .name=="DB_USERNAME" then .value = "new"
else . end)
Avoiding select within the expression on the LHS of |= makes the solution more robust w.r.t. the version of jq being used.
You might like to consider this alternative to using |=:
walk( if type=="object" and .name=="DB_USERNAME"
then .value="new" else . end)

jq json parser concate nested array object value

Hi I have the below JSON file with nested object:
{
"Maps": {
"Campus": [
{
"name": "nus",
"Building": [
{
"name": "sde1",
"Floor": [
{
"name": "floor1"
},
{
"name": "floor2"
}
]
},
{
"name": "sde2"
}
]
},
{
"name": "ntu",
"Building": [
{
"name": "ece1",
"Floor": [
{
"name": "floor1"
},
{
"name": "floor2"
}
]
},
{
"name": "ece2"
}
]
}
]
}
}
I want to use jq to parse the above JSON file and get the below format:
nus>sde1>floor1
nus>sde1>floor2
ntu>ece1>floor1
ntu>ece1>floor2
basically I have to concatenate the Campus Name with Building Name and Floor name and put a < symbol in between.
If the nested object field Floor is not exist, ignore the parse and continue the next child object.
How to achieve that? thanks.
You can use the following jq command:
jq '.Maps.Campus[]|"\(.name)>\(.Building[]|"\(.name)>\(.Floor[]?.name)")"' file.json
jq is smart enough to print the combinations of .name and .Building[].name since .Building is an array. The same action get's applied to .Building[].name and Floor[]?.name. ? because floor is not always set.
Here is a solution which uses jq variables
.Maps.Campus[]
| .name as $campus
| .Building[]
| .name as $bldg
| .Floor[]?
| .name as $floor
| "\($campus)>\($bldg)>\($floor)"