jq: recursion -> nested arrays - bash

How can I parse this json structure with jq? It should loop over leafs (projects and groups) recursively.
My use case is: create project and groups in VCS with CLI. Group can have multiple projects, group can be empty, projects must have parent group created in advance.
Similar analogy would be:
group = folder
project = file
path = absolute path in format /root-groups/nested-groups-level-1/nested-groups-level-2/nested-groups-level-N
Thanks
{
"structure":[
{
"name":"rootgroup1",
"type":"group",
"nested":[
{
"name":"nestedproject1",
"type":"project"
},
{
"name":"nestedgroup1",
"type":"group",
"nested":[
{
"name":"nestednestedproject2",
"type":"project"
}
]
}
]
},
{
"name":"rootproject1",
"type":"project"
},
{
"name":"rootgroup2",
"type":"group",
"nested": []
}
]
}
Expected output:
"rootgroup1","group",""
"nestedproject1","project","rootgroup1"
"nestedgroup1","group","rootgroup1"
"nestednestedproject2","group","rootgroup1/nestedgroup1"
"rootproject1","project",""
"rootgroup2","group",""
Try:
jq -r '.structure[] | .. | "\(.name?) \(.type?)"'
Still not sure, how create a parent path.

The following implements a solution to the problem as I understand it:
# $prefix is an array interpreted as the prefix
def details($prefix):
def out:
select(has("name") and has("type")) | [.name, .type, "/" + ($prefix|join("/"))];
out,
if (.nested | (. and length>0))
then .name as $n | .nested[] | details($prefix + [$n])
else empty
end;
.structure[]
| details([])
| #csv
Given your sample input, the output would be:
"rootgroup1","group","/"
"nestedproject1","project","/rootgroup1"
"nestedgroup1","group","/rootgroup1"
"nestednestedproject2","project","/rootgroup1/nestedgroup1"
"rootproject1","project","/"
"rootgroup2","group","/"
This differs in some respects from the sample output, but hopefully you can take it from here.

Related

Gatsby queries error with multiple queries error [duplicate]

To create the post template through this tutorial(I'm in part 4):
https://www.joaopedro.cc/blog-com-gatsby-e-react-parte-4
But when doing the "PostPage" query an error is occurring:
Multiple "root" queries found: "PostPage" and "PostPage".
Only the first ("PostPage") will be registered.
Instead of:
1 | query PostPage {
2 | markdownRemark {
3 | #...
4 | }
5 | }
6 |
7 | query PostPage {
8 | markdownRemark {
9 | #...
10 | }
11 | }
Do:
1 | query postPageAndPostPage {
2 | markdownRemark {
3 | #...
4 | }
5 | markdownRemark {
6 | #...
7 | }
8 | }
I've looked at other answers to similar questions, and from what I understand it was a case sensitive issue, but from what I see, everything is looking correct.
I'm not getting out of place. :( thanks in advance!
The error message here is actually quite helpful, but it doesn't take it all the way.
The issue is that you can only have a single query and you have multiple. The solution is to query for all the data you need in a single query rather than splitting it across multiple, as the error suggests.
Alas, you're trying to fetch what I imagine are two distinct data sets through the same field, which will give you another error by default. But there's an easy solution here: use aliases to de-duplicate the field name in the result.
query PostPage {
firstPost: markdownRemark {
#...
}
secondPost: markdownRemark {
#...
}
}
I had the same error today on Gatsby. There was one page Query and one Static Query used so the message was a bit misleading.
"This can happen when you use two page/static queries in one file"
The way this was tackled was I traced down the files that were using it. One of them was present but notice it was NOT in any way active in the project. Once it was removed the alert disappeared. So, in a nutshell, even if the file is NOT used anywhere the second identical Static Query is picked up.
In my case it was about having identical names of two page queries in two different pages.
Here's my setup:
in the pages folder, I have two components - index.tsx and account.tsx
both these components have this page query:
export const query = graphql`
query myquery($pathname: String) {
myquery(page: { eq: $pathname }) {
id
title
page
description
}
}
`
I got the error mentioned in the question
So, it turned out two queries in two different files but with the same name is forbidden.
In the end, I just used two different query names in my two files - myquery and myotherquery. Voila!
I just ran into the same error message with a React component that only used one StaticQuery. This component was copied in from another project (where it is working without any issues).
I've looked at other answers to similar questions, and from what I understand it was a case-sensitive issue...
This helped me to solve it - turned out that both the name of the React component as well as the StaticQuery were not propper camelCased (they contained "SEO" - all capital letters).
Error message is gone after cleaning this part up. I thought I share this as I found the error message in this context confusing.
ERROR #85910 GRAPHQL
Multiple "root" queries found: "SEOQuery" and "SEOQuery".
Only the first ("SEOQuery") will be registered.
Instead of:
1 | query SEOQuery {
2 | site {
3 | #...
4 | }
5 | }
6 |
7 | query SEOQuery {
8 | site {
9 | #...
10 | }
11 | }
Do:
1 | query seoQueryAndSeoQuery {
2 | site {
3 | #...
4 | }
5 | site {
6 | #...
7 | }
8 | }

How to Replace Substring in all Elements of Object without Destroying it

I need to replace substrings of all elements in an object.
E.g. replace all 'X' in val1 and val2:
{
"input": [
{
"val1": "008 X 148",
"val2": "SOME X DATA"
},
{
"val1": "X 005 5PM",
"val2": "SOME X DATA"
},
{
"val1": "MODTOX",
"val2": "X SOME X DATA"
}
]
}
My first intention was to use $map and then $each, like this:
$map(input, function($i)
{ $each($i, function($s)
{ $replace($s, "X", "Y" )
})
})
, but as expected, this destroys the object.
Any suggestion? Finally 'input' should still be of same structure.
You need to use the transform operator to modify a copy of the input data:
$ ~> | input | $each(function($v, $n){{$n: $replace($v, "X", "Y") }} ) ~> $merge() |
See https://try.jsonata.org/yeKAKg_U_

How do I merge based on a missing elements with JQ?

I have two JSON files that I am working with: dest_sr.json and src_templates.json
What I would like to is create a new JSON object that combines keys from JSON object #1 (specifically the Template_SR key) and JSON object #2 (the Template_Name and Template_ID keys), but only when a VM-Template cannot be found on Pool_ID that matches on both object #1 and object #2
For the two given JSON objects:
dest_sr.json:
{
"Type": "pool",
"Pool_ID": "adb58e84",
"Template_SR": "fc820294"
}
{
"Type": "pool",
"Pool_ID": "d2dea684",
"Template_SR": "313f2a07"
}
src_templates.json:
{
"Type": "VM-template",
"Template_Name": "CentOS 7 CloudInit V1",
"Template_ID": "9b1833a3",
"Pool_ID": "adb58e84"
}
I would like to create a new JSON object that looks like this:
{
"Template_ID",:"9b1833a3",
"Template_SR":"313f2a07",
"Template_Name":"CentOS 7 CloudInit V1",
}
Using Stackoverflow I was able to hack together a hashJoin from examples that looks like this, where it performs a hashJoin on the .Pool_ID attribute to create a new object:
# hashJoin(a1; a2; field) expects a1 and a2 to be arrays of JSON objects
# and that for each of the objects, the field value is a string.
# A relational join is performed on "field".
def hashJoin(a1; a2; field):
# hash phase:
(reduce a1[] as $o ({}; . + { ($o | field): $o } )) as $h1
| (reduce a2[] as $o ({}; . + { ($o | field): $o } )) as $h2
# join phase:
| reduce ($h1|keys[]) as $key
([]; if $h2|has($key) then . + [ $h1[$key] + $h2[$key] ] else . end) ;
hashJoin( $file1; $file2; .Pool_ID)[]
When I call JQ to perform the hashJoin, I get a new object that looks like this:
/tmp $ jq -nc --slurpfile file1 /tmp/dest_sr.json --slurpfile file2 /tmp/src_templates.json -f hashJoinSimple.jq
{
"Type":"VM-template",
"Pool_ID":"adb58e84",
"Template_SR":"fc820294",
"Template_Name":"CentOS 7 CloudInit V1",
"Template_ID":"9b1833a3"
}
Is there any way I can write my merge so that the object would look like this?
{
"Type":"VM-template",
"Pool_ID":"d2dea684",
"Template_SR":"fc820294",
"Template_Name":"CentOS 7 CloudInit V1",
"Template_ID":"9b1833a3"
}
I think given your particular inputs and your desired result, I don't think the hashing is necessary. It seems like all you need to do is select the inputs from dest_sr.json where it doesn't match the id in src_templates.json, then combine it with the desired values from the template.
$ jq --argfile template src_templates.json '
select(.Pool_ID != $template.Pool_ID) + ($template|{Template_Name,Template_ID})
' dest_sr.json

Golang - nested map doesn't support indexing on inner level, while outer is fine

Almost a go-newbie, and for the first time I have to made a question about it, about a problem with interfaces, types and maps.
So, my starting point is a database query that retrieves an object like this one:
+-------------+---------------------+----------+------------+
| category_id | category_name | group_id | group_name |
+-------------+---------------------+----------+------------+
| 1 | Category1 | 1 | Group1 |
| 1 | Category1 | 2 | Group2 |
| 1 | Category1 | 3 | Group3 |
| 1 | Category2 | 4 | Group4 |
| 2 | Category2 | 5 | Group5 |
+-------------+---------------------+----------+------------+
and my final goal is having a json object with the groups under the same category under that category, like this one:
{
"id": 1,
"name": "category1",
"groups": [
{
"id": 1,
"name": "Group1"
},
{
"id": 2,
"name": "Group2"
},
{
"id": 3,
"name": "Group3"
}
]
},
{
"id": 2,
"name": "Category2",
"groups": [
{
"id": 4,
"name": "Group4"
},
{
"id": 5,
"name": "Group5"
}
]
}
I don't want to use multiple queries, cause this is just a part of the final query, I used just 2 field to be more clear with this example. In my current situation I just have 5 levels...
So I created a struct that should be used on all levels of my object, that implements an interface:
type NestedMapObjs interface {
getOrderedKeys() []int
}
and the type that implements this interface, that should be a map of int in order to append elements to the correct map:
type BuilderMapObjs map[int]NestedMapObj
when NestedMapObject is:
type NestedMapObj struct {
ID int
Name *string
NestedObj NestedMapObjs
}
so, on my method that builds the map object that I want, I have no problem to add the first level of my object (Category) but, I found some problems on the second level, the group one. In particular, this is my function that adds a new row:
func (m BuilderMapObjs) addNewRow(scanned audienceBuilderScannedObject) error {
if _, ok := m[scanned.CategoryID]; !ok {
var innerObjs BuilderMapObjs
innerObjs = make(BuilderMapObjs, 0)
m[scanned.CategoryID] = NestedMapObj{
ID: scanned.CategoryID,
Name: &scanned.CategoryName,
NestedObj: innerObjs,
}
}
if _, ok := m[scanned.CategoryID].NestedObj[scanned.GroupID]; !ok {
m[scanned.CategoryID].NestedObj[scanned.GroupID] = NestedMapObj{
ID: scanned.GroupID,
Name: &scanned.GroupName,
}
}
return nil
}
(I know, I can refactor and make this code more readable, but this is not the point now...)
The problem is when I try to get the inner object by its key, and when I try to add it. This line:
m[scanned.CategoryID].NestedObj[scanned.GroupID]
produce this error: invalid operation: m[scanned.CategoryID].NestedObj[scanned.GroupID] (type NestedMapObjs does not support indexing)
Actually, I just found that with a better implementation, implementing two more methods in the interface (getIndex and addToIndex) I fixed the problem, but I'd like to understand this problem.
Why I have an error on the inner object and not on the outer one?
Thanks for reading until this point!

Number of string value occurrences for distinct another column value

I have a model Counter which returns the following records:
name.....flowers.....counter
vino.....rose.........1
vino.....lily.........1
gaya.....rose.........1
rosi.....lily.........1
vino.....lily.........1
rosi.....rose.........1
rosi.....rose.........1
I want to display in the table like:
name | Rose | Lily |
---------------------
Vino | 1 | 2 |
---------------------
Gaya | 1 | 0 |
---------------------
Rosi | 2 | 1 |
I want to display the count of flowers for each distinct name. I have tried the following and wondering how can I do it elegantly?
def counter_results
#counter_results= {}
Counter.each do |name|
rose = Counter.where(flower: 'rose').count
lily= Counter.where(flower: 'lily').count
#counter_results['name'] = name
#counter_results['rose_count'] = rose
#counter_results['lily_count'] = lily
end
return #counter_results
end
which I don't get the hash values.
This will give you slightly different output, but I think it is probably closer to what you want than what you showed.
You can use the query:
Counter.group([:name, :flowers]).sum(:counter)
To get a result set that looks like:
{ ["vino", "rose"] => 1, ["vino", "lily"] => 2, ["gaya", "rose"] => 1, ["gaya", "lily"] => 0, ... }
And you can do something like this to generate your hash:
def counter_results
#counter_results = {}
Counter.group([:name, :flowers]).sum(:counter).each do |k, v|
#counter_results[k.join("_")] = v
end
#counter_results
end
The resulting hash would look like this:
{
"vino_rose" => 1,
"vino_lily" => 2,
"gaya_rose" => 1,
"gaya_lily" => 0,
...
}
Somebody else may have a better way to do it, but seems like that should get you pretty close.

Resources