Add key value to parent subelement if child has specific key:value - json

I'm trying to understand what's the best way to add a json element to child's parent
if that child contains a specific key:value and finally print the entire json using jq
I try to explain better with an example.
The input json is:
{
"family": {
"surname": "Smith"
},
"components": [
{
"name": "John",
"details": {
"hair": "brown",
"eyes": "brown",
"age": "56"
},
"role": "father"
},
{
"name": "Mary",
"details": {
"hair": "blonde",
"eyes": "green",
"age": "45"
},
"role": "mother"
},
{
"name": "George",
"details": {
"hair": "blonde",
"eyes": "brown",
"age": "25"
},
"role": "child"
}
]
}
I want to add:
"description": "5 years less than 30"
at the same level of "details" if "age" is equal to "25" and then print the result:
{
"family": {
"surname": "Smith"
},
"components": [
{
"name": "John",
"details": {
"hair": "brown",
"eyes": "brown",
"age": "56"
},
"role": "father"
},
{
"name": "Mary",
"details": {
"hair": "blonde",
"eyes": "green",
"age": "45"
},
"role": "mother"
},
{
"name": "George",
"details": {
"hair": "blonde",
"eyes": "brown",
"age": "25"
},
"role": "child",
"description": "5 years less than 30"
}
]
}
The only solution I've found was to apply the update but printing only the "components" content;
then I've removed from the JSON and finally inserted the modified "components" content previously saved, in this way:
cat sample.json | jq -c ' .components[] | select(.details.age=="25") |= . + {description: "5 years less than 30" } ' > /tmp/saved-components.tmp
cat sample.json | jq --slurpfile savedcomponents /tmp/saved-components.tmp 'del(.components) | . + { components: [ $savedcomponents ] }'
I don't think it's the best way to solve these kind of problems, so I'd like to know what is
the right "jq approach".
I forgot to say: I prefer to use jq only, not other tools
Than you
Marco

You can select the object matching the condition and append to that object. Something like below. The key is to use += the modification assignment to not lose the other objects
(.components[] | select(.details.age == "25")) += { "description": "5 years less than 30" }
jqplay - Demo

Here's a straightforward ("no magic") and efficient solution:
.components |=
map(if .details.age=="25" then .description = "5 years less than 30" else . end)

Related

JQ - unique count of each value in an array

I'm needing to solve this with JQ. I have a large lists of arrays in my json file and am needing to do some sort | uniq -c types of stuff on them. Specifically I have a relatively nasty looking fruit array that needs to break down what is inside. I'm aware of unique and things like that, and imagine there is likely a simple way to do this, but I've been trying run down assigning things as variables and appending and whatnot, but I can't get the most basic part of counting the unique values per that fruit array, and especially not without breaking the rest of the content (hence the variable ideas). Please tell me I'm overthinking this.
I'd like to turn this;
[
{
"uid": "123abc",
"tID": [
"T19"
],
"fruit": [
"Kiwi",
"Apple",
"",
"",
"",
"Kiwi",
"",
"Kiwi",
"",
"",
"Mango",
"Kiwi"
]
},
{
"uid": "456xyz",
"tID": [
"T15"
],
"fruit": [
"",
"Orange"
]
}
]
Into this;
[
{
"uid": "123abc",
"tID": [
"T19"
],
"metadata": [
{
"name": "fruit",
"value": "Kiwi - 3"
},
{
"name": "fruit",
"value": "Mango - 1"
},
{
"name": "fruit",
"value": "Apple - 1"
}
]
},
{
"uid": "456xyz",
"tID": [
"T15"
],
"metadata": [
{
"name": "fruit",
"value": "Orange - 1"
}
]
}
]
Using group_by and length would be one way:
jq '
map(with_entries(select(.key == "fruit") |= (
.value |= (group_by(.) | map(
{name: "fruit", value: "\(.[0] | select(. != "")) - \(length)"}
))
| .key = "metadata"
)))
'
[
{
"uid": "123abc",
"tID": [
"T19"
],
"metadata": [
{
"name": "fruit",
"value": "Apple - 1"
},
{
"name": "fruit",
"value": "Kiwi - 4"
},
{
"name": "fruit",
"value": "Mango - 1"
}
]
},
{
"uid": "456xyz",
"tID": [
"T15"
],
"metadata": [
{
"name": "fruit",
"value": "Orange - 1"
}
]
}
]
Demo

Group related items then merge them and accumulate some properties into arrays

Have a JSON as below,
[
{
"first": "foo",
"last": "bar",
"roll": "32",
"subject": "maths"
},
{
"first": "joe",
"last": "mighty",
"roll": "31",
"subject": "english"
},
{
"first": "foo",
"last": "bar",
"roll": "32",
"subject": "english"
},
{
"first": "joe",
"last": "mighty",
"roll": "31",
"subject": "maths"
},
{
"first": "foo",
"last": "bar",
"roll": "32",
"subject": "science"
}
]
unique_by(.first,.last, .roll) will remove other subject entries. I want convert 'subject' to an array with all the values in the source JSON array. How to map using jq for creating nested array of subject as below,
[
{
"first": "foo",
"last": "bar",
"roll": "32",
"subject": ["maths", "english", "science"]
},
{
"first": "joe",
"last": "mighty",
"roll": "31",
"subject": ["english", "maths"]
}
]
JQ doesn't have a nice merge built-in for such tasks yet, but you can achieve this one using group_by.
group_by(.first, .last, .roll) | map(
(.[0] | {first, last, roll}) + {subject: map(.subject)}
)
Online demo
A variation based on Oguz's excellent answer:
group_by(.first, .last, .roll)
| map( map(.subject) as $subject
| .[0]
| {first, last, roll, $subject})
Collect related items (group by first, last and roll) and reduce each collection into a single item:
First we put all subjects into $subject
Take the first item as a "template" but rewrite the .subject key with $subject.

Need to find key-value pair and replace key-value pair in JSON using JQ

I have this JSON
{
"firstName": "Rajesh",
"lastName": "Kumar",
"gender": "man",
"age": 24,
"address": {
"streetAddress": "126 Udhna",
"city": "Surat",
"state": "WB",
"postalCode": "394221"
},
"phoneNumbers": [
{
"type": "home",
"number": "7383627627"
}
]
}
I need to find the value of the "state" key Using JQ and replace the value in JSON. I do not want to fetch it by providing the position of the key, Like
firstName=$(cat sample-json.json | jq -r '.firstName')
My expected output
{
"firstName": "Rajesh",
"lastName": "Kumar",
"gender": "man",
"age": 24,
"address": {
"streetAddress": "126 Udhna",
"city": "Surat",
"state": "Bihar",
"postalCode": "394221"
},
"phoneNumbers": [
{
"type": "home",
"number": "7383627627"
}
]
}
If you're willing to specify .address:
jq '.address.state = "Bihar"' sample-json.json
Otherwise:
jq 'walk(if type == "object" and has("state") then .state = "Bihar" else . end)' sample-json.json
This last will replace all .state values. If you only want to replace the first occurrence:
jq 'first(..|objects|select(has("state"))).state = "Bihar"' sample-json.json
And so on. It would really help all concerned if you could make the requirements clear.

Map conditional child elements

I am working with a JSON file which has contains lot of data that can be removed before sending to an API.
Found that JQ can be used to achieve this but not sure on how to map to get the desired results.
Input JSON
{
"name": "Sample name",
"id": "123",
"userStory": {
"id": "234",
"storyName": "Story Name",
"narrative": "Narrative",
"type": "feature"
},
"testSteps": [
{
"number": 1,
"description": "Step 1",
"level": 0,
"children": [
{
"number": 2,
"description": "Description",
"children": [
{
"number": 3,
"description": "Description"
}
]
},
{
"number": 4,
"anotherfield": "another field"
}
]
}
]
}
Desired Output
{
"name": "Sample name",
"userStory": {
"storyName": "Story Name"
},
"testSteps": [
{
"description": "Step 1",
"children": [
{
"description": "Description",
"children": [
{
"description": "Description"
}
]
},
{
"anotherfield": "anotherfield"
}
]
}
]
}
Tried to do it with the following jq command
map_values(..|{name, id, userStory})
but not sure how to filter only the userStory.storyName.
Thanks in advance.
Note: The actual JSON has different child elements that are repeated in some cases.
To delete .id from root object:
del(.id)
To leave only .storyName in .userStory:
.userStory |= {storyName}
To delete .number and .level from every object on any level in .testSteps:
.testSteps |= walk(if type == "object" then del(.number, .level) else . end)
Putting it all together:
del(.id) | (.userStory |= {storyName}) | (.testSteps |=
walk(if type == "object" then del(.number, .level) else . end))
Online demo

bash JQ. How can modify a key value pair from json file containing list of objects?

I am using jq to work on a large json file. It looks something like this:
FILE1.json
{
"person": [
{
"name": "sam",
"age": "40",
"weight": "180",
"height": "6"
},
{
"name": "peter",
"age": "41",
"weight": "180",
"height": "6.1"
},
{
"name": "mike",
"age": "40",
"weight": "200",
"height": "5.9"
},
{
"name": "ethan",
"age": "41",
"weight": "190",
"height": "6"
}
]
}
I want to use jq tool to change the value of weight from 200 to 195 where name is "mike".
How can i do this?
The idea is to update the person array where the object that has the name "mike" will be modified to have the weight "195". Otherwise it's just skipped.
.person |= map(
if .name == "mike"
then .weight = "195"
else .
end)
Or more concisely, search for the persons to update and update them:
(.person[] | select(.name == "mike")).weight = "195"