Using jq to conditionally flatten recursive json - recursion

The simplest version of the input document I could come up with is
{
"references": [
{
"version": 5,
"id": "id1",
"objType": "A"
},
{
"version": 4,
"id": "id2",
"objType": "B",
"referencing": []
},
{
"version": 4,
"id": "id3",
"objType": "B",
"referencing": [
{
"version": 2,
"id": "id4",
"objType": "A"
},
{
"version": 3,
"id": "id5",
"objType": "B",
"referencing": []
}
]
}
]
}
Objects of type A have no referencing objects.
Objects of type B can be referenced by either type of object.
There are two outputs I need from this json:
Output #1 is the version info for objects of type A with the id value as a key with the value of version. A objects can be at the top level or at some arbitrary depth in the referencing arrays.
{
"references": {
"id1": {"version": 5},
"id4": {"version": 2}
}
}
The 2nd output is similar: the version info for objects of type B. The can be a chain of type B objects referencing other type B objects.
{
"references": {
"id2": {"version": 4},
"id3": {"version": 4},
"id5": {"version": 3}
}
}

Use recursive decsent operator and from_entries. You don't need to follow the "references" (at least not to produce the expected output in your question)
{
dependencies: [.. | select(.objType=="A")? | { key: .id, value: {version} }] | from_entries
},
{
dependencies: [.. | select(.objType=="B")? | { key: .id, value: {version} }] | from_entries
}
Output:
{
"dependencies": {
"id1": {
"version": 5
},
"id4": {
"version": 2
}
}
}
{
"dependencies": {
"id2": {
"version": 4
},
"id3": {
"version": 4
},
"id5": {
"version": 3
}
}
}
It's also possible to merge (add) objects instead of constructing them from their entries, which makes the code minimally shorter:
{
dependencies: [.. | select(.objType=="A")? | { (.id): {version} }] | add
}

You can use recurse to traverse the document, INDEX to create an object with IDs as keys, map_values to format their values using select to reduce according to your criteria.
jq --arg type A '
.references |= (
INDEX(.[] | recurse(.referencing[]?); .id)
| map_values(select(.objType == $type) | {version})
)
'
{
"references": {
"id1": {
"version": 5
},
"id4": {
"version": 2
}
}
}
Demo
This works for both questions, provide A or B to --arg type.
Note that this is using the error suppression operator ? when recursing down. If you want to restrict the traversal explicitly to .objType == "B", just prepend it in a select expression, i.e. replace recurse(.referencing[]?) with recurse(select(.objType == "B") | .referencing[]). Demo

Related

Is there a way to transform these 2 arrays by using jq, into a set of objects, like in the example down below?

Example json data:
{
"data": [
{
"place": "FM346",
"id": [
"7_day_A",
"7_day_B",
"7_day_C",
"7_day_D"
],
"values": [
0,
30,
23,
43
]
},
{
"place": "LH210",
"id": [
"1_day_A",
"1_day_B",
"1_day_C",
"1_day_D"
],
"values": [
4,
45,
100,
9
]
}
]
}
what i need to transform it into:
{
"data": [
{
"place": "FM346",
"7_day_A": {
"value": 0
},
"7_day_B": {
"value": 30
},
"7_day_C": {
"value": 23
},
"7_day_D": {
"value": 43
}
},
{
"place": "LH210",
"1_day_A": {
"value": 4
},
"1_day_B": {
"value": 45
},
"1_day_C": {
"value": 100
},
"1_day_D": {
"value": 9
}
}
]
}
i have tried this:
{
data:[.data |.[]|
{
place: (.place),
(.id[]):
{
value: (.values[])
}
}]
}
(in jqplay: https://jqplay.org/s/f4BBtN9gwmp)
and this:
{
data:[.data |.[]|
{
place: (.place),
test:
[{
(.id[]):
{
value: (.values[])
}
}]
}]
}
(in jqplay: https://jqplay.org/s/pKIvQe1CzgX)
but they arent grouped in the way i wanted and it gives each value to each id, not the corresponding one.
I have been trying for some time now, but im new to jq and have no idea how to transform it this way, thanks in advance for any answers.
You can use transpose here, which can play a key role in converting the arrays to key/value pairs
.data[] |= {place} +
([ .id, .values ] | transpose | map({(.[0]): { value: .[1] } }) | add)
The solution works by converting the array-of-arrays [.id, .values] by transposing them, i.e. converting
[["7_day_A","7_day_B","7_day_C","7_day_D"],[0,30,23,43]]
[["1_day_A","1_day_B","1_day_C","1_day_D"],[4,45,100,9]]
to
[["7_day_A",0],["7_day_B",30],["7_day_C",23],["7_day_D",43]]
[["1_day_A",4],["1_day_B",45],["1_day_C",100],["1_day_D",9]]
With the transformation done, we construct an object with key as the zeroth index element and value as an object comprising of the value of first index element, and combine the results together with add
Demo - jqplay

JQ does not contain for "key": "value" JSON structure

The question is similar to THIS, but the JSON structure is different.
In my usecase JSON has an arrays with key:value data:
{
"data":[
{
"name":"banana",
"tags":[
{
"id":"yellow"
},
{
"id":"long"
}
]
},
{
"name":"apple",
"tags":[
{
"id":"red"
},
{
"id":"round"
}
]
},
{
"name":"orange",
"tags":[
{
"id":"orange"
},
{
"id":"round"
},
{
"id":"colored"
}
]
}
]
}
What required is to filter the only elements that do not have certain keyword - "red" for instance.
When i use jq '.data[] | select(.tags[].id | index( "red" ))' it brings me the correct resut of 'apple' (as it has "id": "red") and 'orange' (as it has "id": "colored").
However, when i add the negation jq '.data[] | select(.tags[].id | index( "red" ) | not)' the results are more than strange, with elements' duplication, totally enigmatic.
How can i use jq to filter the result the way it returns only elements that do not have the exact match among the array values?
You can use all to give a condition all items must meet. Here, all values in .tags[].id must be unequal != to "red":
jq '.data[] | select(all(.tags[].id; . != "red"))'
{
"name": "banana",
"tags": [
{
"id": "yellow"
},
{
"id": "long"
}
]
}
{
"name": "orange",
"tags": [
{
"id": "orange"
},
{
"id": "round"
},
{
"id": "colored"
}
]
}
Demo
Add .name and use the -r option to only get the names:
jq -r '.data[] | select(all(.tags[].id; . != "red")).name'
banana
orange
Demo
index works on an array, your current filter does not pass index to an array, therefore you're getting other results then expected.
.data[] | select([ .tags[].id ] | index("red") | not)
Here we create an array with all the id's [ .tags[].id ] and use that array to check for red: | index("red") | not
The above filter gives the following output:
{
"name": "banana",
"tags": [
{
"id": "yellow"
},
{
"id": "long"
}
]
}
{
"name": "orange",
"tags": [
{
"id": "orange"
},
{
"id": "round"
},
{
"id": "colored"
}
]
}
Demo
If you want to exclude 'colored', use contains():
.data[] | select([ .tags[].id ] | contains(["red"]) | not)
{
"name": "banana",
"tags": [
{
"id": "yellow"
},
{
"id": "long"
}
]
}
Demo

Conditional append a element to an array

Here is the jq I have, it just wants to build a new element and then append it to an array,
[.[] | . as { foo: $foo1, bar: $bar1} |
{
names: ([
$foo1 | range(0;length) as $i |
{ key: ($foo1[$i]) }
] + [{ key: $bar1 }])
} |
{
values: .names,
}
]
And suppose I have a json like this,
{
"foo":[
"key1",
"key2"
],
"bar": "key3"
}
This will generate a json file like this,
[
{
"values": [
{
"key": "key1"
},
{
"key": "key2"
},
{
"key": "key3"
}
]
}
]
But the element should be only appended when the $bar is not an empty string, can I do something like this?
[.[] | . as { foo1: $foo1, bar1: $bar1 if $bar != ""}
...
or do it when is appended,
names: ([
$foo1 | range(0;length) as $i |
{ key: ($foo1[$i]) }
] + [{ key: $bar1 }] | if $bar != "")
thanks in advance for any help!
You could just use select to filter out that case
jq '[{values: (.foo + [.bar | select(. != "")]) | map({key:.})}]'
If .bar == "key3", it prints
[
{
"values": [
{
"key": "key1"
},
{
"key": "key2"
},
{
"key": "key3"
}
]
}
]
If .bar == "", it prints
[
{
"values": [
{
"key": "key1"
},
{
"key": "key2"
}
]
}
]
If .bar does not exist, it will print
[
{
"values": [
{
"key": "key1"
},
{
"key": "key2"
},
{
"key": null
}
]
}
]
If in this last case you want to have the same result as with .bar == "", then change . != "" to values != "" in the filter to consider only values that are not null (or to strings != "" to only consider (non-empty) strings and disregard any other type).

JQ: How do I extract item which has part of subitem value of "FOO" [duplicate]

I have a JSON file that looks like this:
{
"InstanceId": "i-9KwoRGF6jbhYdZi823aE4qN",
"Tags": [
{
"Key": "blah",
"Value": "server-blah"
},
{
"Key": "environment",
"Value": "ops"
},
{
"Key": "server_role",
"Value": "appserver"
},
{
"Key": "Name",
"Value": "some_name"
},
{
"Key": "product",
"Value": "some_server"
}
]
}
{
...more objects like the above...
}
I need to display the InstanceId where "Key" == "environment" and "Value" == "ops".
I have jq-1.6.
If I say:
cat source.json | jq '
{ InstanceId, Tags } |
(.Tags[] | select( .Key == "environment" ))
'
I get some of what I want, but I cannot figure out how to include InstanceId in the output nor how to incorporate the "and" part of the select.
Here is a simple but efficient approach using any:
select( any(.Tags[]; .Key=="environment" and .Value == "ops") )
| .InstanceId
An alternative approach that avoids .Tags[]:
{"Key": "environment", "Value": "ops"} as $object
| select( .Tags | index($object) )
| .InstanceId
I'm not sure if this is the exact output you're looking for (comment if it isn't), but this will output the InstanceIds of JSON objects that contain a Tag with Key environment and Value ops.
jq 'select( .Tags[] | (.Key == "environment" and .Value == "ops")) | .InstanceId' < source.json

Create an object with specified indexes

I am trying to use for loop for every object using jq.
Sample Input generated by Elasticsearch
{
"took": 202,
"timed_out": false,
"aggregations": {
"aggsDateHistogram": {
"buckets": [
{
"key": 1465974236000,
"search": {
"value": 14
}
},
{
"key": 1465975137000,
"search": {
"value": 16
}
}
]
}
}
}
I want to have an object that has a key value and corresponding value of value index from search.
{ "date": .aggregations.aggsDateHistogram.buckets[].key, "value": .aggregations.aggsDateHistogram.buckets[].search.value }
This gives me an object but with cartesian product, but I only want to have values like
key[1] : search[1].value
key[2] : search[2].value
So you want to produce this output?
[
{
"key": 1465974236000,
"value": 14
},
{
"key": 1465975137000,
"value": 16
}
]
The following will do just that:
.aggregations[].buckets
| map({key: .key, value: .search.value})
And from a terminal:
jq '.aggregations[].buckets
| map({key: .key, value: .search.value})' input.json
Here is a slightly simpler solution
[ .aggregations[].buckets[] | {key, value:.search.value} ]

Resources