Is there a way to use default in object construction with jq? - json

I want to filter and assign a value from array based on a condition, and use default in case if array does not have the matched object.
Here is a sample object:
{
"array" : [
{
"id": "A",
"conversations": [
{
"conversation": "1",
"type": "good"
},
{
"conversation": "2",
"type": "bad"
}
]
},
{
"id": "B",
"conversations": [
{
"conversation": "3",
"type": "good"
},
{
"conversation": "4",
"type": "bad"
}
]
},
{
"id": "C",
"conversations": [
{
"conversation": "5",
"type": "bad"
},
{
"conversation": "6",
"type": "bad"
}
]
}
]
}
Required output:
{
"id": "A",
"goodConversation": "1"
}
{
"id": "B",
"goodConversation": "3"
},
{
"id": "C",
"goodConversation": null
}
echo of my input:
echo '{"array":[{"id":"A","conversations":[{"conversation":"1","type":"good"},{"conversation":"2","type":"bad"}]},{"id":"B","conversations":[{"conversation":"3","type":"good"},{"conversation":"4","type":"bad"}]},{"id":"C","conversations":[{"conversation":"5","type":"bad"},{"conversation":"6","type":"bad"}]}]}'
I tried running following jq
jq '.array[] | {id, "goodConversation": .conversations[] | select(.type == "good") | .conversation}'
Actual output:
{
"id": "A",
"goodConversation": "1"
}
{
"id": "B",
"goodConversation": "3"
}
since the object with id: "C" does not have any good conversation the whole object gets filtered out. Is there a way to create the output object which contains "C" with null as value?
Clarification:
"conversations" will have at most one good conversation.
I am using jq 1.5

One way to provide a default value is often to use the // "alternative" operator. Building on the foundations you've laid, you could write:
.array[]
| {id,
"goodConversation":
((.conversations[]
| select(.type == "good")
| .conversation) // null) }
If there is more than one "good" conversation, however, this may not be exactly what you want. If it's not, then consider using first, e.g.:
.array[]
| {id,
"goodConversation":
( first(.conversations[]
| select(.type == "good")
| .conversation) // null)}

Related

How to make jq to pick name value pairs

Might be more or less the same ask as How to get JQ name/value pair from nested (array?) response?, but that question and example there is way too convoluted than what I'm asking --
Giving the input jason as in https://jqplay.org/s/jyKBnpx9NYX
Pick out all the name/value pair under .QueryString, .Params into the same unnested array
E.g., for an input of
{
"Some": "Random stuff",
"One": {
"QueryString": [
{ "Name": "IsOrdered", "Value": "1" },
{ "Name": "TimeStamp", "Value": "11654116426247" }
]
},
"Two": {
"QueryString": [
{ "Name": "IsOrdered", "Value": "1" },
{ "Name": "TimeStamp", "Value": "11654116426247" }
]
},
"Params": [
{ "Name": "ClassName", "Value": "PRODUCT" },
{ "Name": "ListID", "Value": "Products" },
{ "Name": "Mode ", "Value": "1" },
{ "Name": "Dept" , "Value": "5" },
{ "Name": "HasPrevOrder", "Value": "" }
],
"And": {
"QueryString":[]
},
"More": "like",
"More+": "this"
}
The output would be:
[
{
"Name": "IsOrdered",
"Value": "1"
},
{
"Name": "TimeStamp",
"Value": "11654116426247"
},
{
"Name": "IsOrdered",
"Value": "1"
},
{
"Name": "TimeStamp",
"Value": "11654116426247"
},
{
"Name": "ClassName",
"Value": "PRODUCT"
},
{
"Name": "ListID",
"Value": "Products"
},
...
],
without any empty arrays output ([]), while keep the repeated values in the array.
I tried to remove empty arrays output ([]) by changing the jq expression from
[( .. | objects | ( .QueryString, .Params ) | select( . != null) )]
to
[( .. | objects | ( .QueryString, .Params ) | select( . != null && . != []) )]
but it failed.
And the final output need to be unnested into a single array too.
Bonus Q: Would it be possible to output each name/value pair on one line of their own like the following?
{ "Name": "IsOrdered", "Value": "1" },
{ "Name": "TimeStamp", "Value": "11654116426247" },
{ "Name": "IsOrdered", "Value": "1" },
{ "Name": "TimeStamp", "Value": "11654116426247" },
To get the Name/Value objects, one per line, you could go with:
jq -c '.. | objects | (.QueryString, .Params) | .. | objects | select( .Name and .Value)'
or more cavalierly:
jq -c '.. | objects | select( .Name and .Value)'
The && must be replaced with and. On the result you can use | flatten to convert "array of arrays of objects" into just "array of objects".
Bonus A: Use the -c/--compact-output flag of jq together with | flatten[] instead of just | flatten.
Together:
jq -c '
[
..
| objects
| ( .QueryString, .Params )
| select(. != null and . != [])
]
| flatten[]' input.json
Although this expression can be simplified into .. | objects | .QueryString[]?, .Params[]?
The output is:
{"Name":"ClassName","Value":"PRODUCT"}
{"Name":"ListID","Value":"Products"}
{"Name":"Mode ","Value":"1"}
{"Name":"Dept","Value":"5"}
{"Name":"HasPrevOrder","Value":""}
{"Name":"IsOrdered","Value":"1"}
{"Name":"TimeStamp","Value":"11654116426247"}
{"Name":"IsOrdered","Value":"1"}
{"Name":"TimeStamp","Value":"11654116426247"}

Using jq to search for a value based on a key located deep in json file

I am new to jq and I'm trying to use it to search for a value in a json file based on a key that is located deep in the json structure. Here is a sample of my json file:
{
"data": {
"inventory": {
"location": "remote",
"list": {
"content": [
{
"item": {
"name": "minivan"
},
"owner": {
"id": "12345",
"state": "CA"
}
},
{
"item": {
"name": "sedan"
},
"owner": {
"id": "67890",
"state": "AZ"
}
}
]
}
}
}
}
An example of search that I'm trying to do is:
select item.name where owner.id = "67890"
and the expected output would be:
item.name = "sedan"
I'm trying to run the following:
jq '.[] | select .owner.id = "67890" | .item.name' json
and it generates an error:
jq: error: select/0 is not defined at <top-level>, line 1:
.[] | select .owner.id = "67890" | .item.name
jq: 1 compile error
Any pointers on how to do this in jq would be much appreciated!
Thanks!
First, you have to "navigate" to where you want to make the query. This seems to be an array.
.data.inventory.list.content
[
{
"item": {
"name": "minivan"
},
"owner": {
"id": "12345",
"state": "CA"
}
},
{
"item": {
"name": "sedan"
},
"owner": {
"id": "67890",
"state": "AZ"
}
}
]
Demo
Next, let's iterate over that array's items, which gives us a stream of objects.
.[]
{
"item": {
"name": "minivan"
},
"owner": {
"id": "12345",
"state": "CA"
}
}
{
"item": {
"name": "sedan"
},
"owner": {
"id": "67890",
"state": "AZ"
}
}
Demo
From these objects we select those that match your criteria.
select(.owner.id == "67890")
{
"item": {
"name": "sedan"
},
"owner": {
"id": "67890",
"state": "AZ"
}
}
Demo
Finally, we extract the value you're interested in.
.item.name
"sedan"
Demo
Everything combined in a jq call would be:
jq '.data.inventory.list.content[] | select(.owner.id == "67890").item.name'
"sedan"
Demo
This output is still valid JSON document (containing nothing but a JSON string). If you want to process the output as raw text, use the --raw-output (or -r) option:
jq -r '.data.inventory.list.content[] | select(.owner.id == "67890").item.name'
sedan
Demo
Here's a solution that avoids having to "navigate" to the right place, and which is also quite close to your SQL-like query:
..
| objects
| select(.owner and
(.owner|type=="object" and .id == "67890"))
.item.name
or more succinctly:
..|objects|select(.owner.id? == "67890").item.name

need to extract specific string with JQ

I have a JSON file (see below) and with JQ I need to extract the resourceName value for value = mail#mail1.com
So in my case, the result should be name_1
Any idea to do that ?
Because this does not work :
jq '.connections[] | select(.emailAddresses.value | test("mail#mail1.com"; "i")) | .resourceName' file.json
{
"connections": [
{
"resourceName": "name_1",
"etag": "123456789",
"emailAddresses": [
{
"metadata": {
"primary": true,
"source": {
"type": "CONTACT",
"id": "123456"
}
},
"value": "mail#mail1.com",
}
]
},
{
"resourceName": "name_2",
"etag": "987654321",
"emailAddresses": [
{
"metadata": {
"primary": true,
"source": {
"type": "CONTACT",
"id": "654321"
},
"sourcePrimary": true
},
"value": "mail#mail2.com"
}
]
}
],
"totalPeople": 187,
"totalItems": 187
}
One solution is to store the parent object while selecting on the child array:
jq '.connections[] | . as $parent | .emailAddresses // empty | .[] | select(.value == "mail#mail1.com") | $parent.resourceName' file.json
emailAddresses is an array. Use any if finding one element that matches will suffice.
.connections[] | select(any(.emailAddresses[];.value == "mail#mail1.com")).resourceName

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

Using jq to convert object to key with values

I have been playing around with jq to format a json file but I am having some issues trying to solve a particular transformation. Given a test.json file in this format:
[
{
"name": "A", // This would be the first key
"number": 1,
"type": "apple",
"city": "NYC" // This would be the second key
},
{
"name": "A",
"number": "5",
"type": "apple",
"city": "LA"
},
{
"name": "A",
"number": 2,
"type": "apple",
"city": "NYC"
},
{
"name": "B",
"number": 3,
"type": "apple",
"city": "NYC"
}
]
I was wondering, how can I format it this way using jq?
[
{
"key": "A",
"values": [
{
"key": "NYC",
"values": [
{
"number": 1,
"type": "a"
},
{
"number": 2,
"type": "b"
}
]
},
{
"key": "LA",
"values": [
{
"number": 5,
"type": "b"
}
]
}
]
},
{
"key": "B",
"values": [
{
"key": "NYC",
"values": [
{
"number": 3,
"type": "apple"
}
]
}
]
}
]
I have followed this thread Using jq, convert array of name/value pairs to object with named keys and tried to group the json using this expression
jq '. | group_by(.name) | group_by(.city) ' ./test.json
but I have not been able to add the keys in the output.
You'll want to group the items at the different levels and building out your result objects as you want.
group_by(.name) | map({
key: .[0].name,
values: (group_by(.city) | map({
key: .[0].city,
values: map({number,type})
}))
})
Just keep in mind that group_by/1 yields groups in a sorted order. You'll probably want an implementation that preserves that order.
def group_by_unsorted(key_selector):
reduce .[] as $i ({};
.["\($i|key_selector)"] += [$i]
)|[.[]];