How can I count number of elements after query? - jq

I have a query:
.modules[].resources | select (.[]!=null)
and after it I have got:
{ somestuff } { somestuff } { somestuff }
when I add legth after all:
.modules[].resources | select (.[]!=null) | length
I have got:
1 1 1
but I need to count elements, so I need 3 in an output. How can I implement it ?
In fact it would be very useful to create an array from the first query output to operate with it furthure
[ { somestuff } , { somestuff } , { somestuff } ]

You can put the results of the query into a list and get the length of this list:
[ .modules[].resources | select (.[]!=null) ] | length

Since you indicated it would be very useful to create an array from the first query output, what you probably want to use here is map.
Let's assume your data is something like
the data from this question: jq: search by value from another array element
{
"modules": [
{
"resources": [
{
"type": "openstack_compute_instance_v2",
"primary": {
"id": "5edfe2bf-94df-49d5-8118-3e91fb52946b",
"attributes": {
"name": "jumpbox"
}
}
},
{
"type": "openstack_compute_floatingip_associate_v2",
"primary": {
"attributes": {
"instance_id": "5edfe2bf-94df-49d5-8118-3e91fb52946b",
"floating_ip": "10.120.241.21"
}
}
}
]
}
]
}
with this data your filter
.modules[].resources
| select (.[]!=null) #< do you really want this `[]` ?
would produce two copies of the entire .resources array. What you may want instead is
.modules[].resources
| map(select(.!=null))
which will give you an array
[
{
"type": "openstack_compute_instance_v2",
"primary": {
"id": "5edfe2bf-94df-49d5-8118-3e91fb52946b",
"attributes": {
"name": "jumpbox"
}
}
},
{
"type": "openstack_compute_floatingip_associate_v2",
"primary": {
"attributes": {
"instance_id": "5edfe2bf-94df-49d5-8118-3e91fb52946b",
"floating_ip": "10.120.241.21"
}
}
}
]
to get the length, just add length:
.modules[].resources
| map(select(.!=null))
| length
in this example giving
2
Because map(f) is defined as [ .[] | f ] the above filter is really
.modules[].resources
| [
.[]
| select(.!=null)
]
| length
In this form you can clearly see the construction of the intermediate array.
Note also that jq provides a built-in values filter which is defined as select(.!=null) so this example could be further simplified to just
.modules[].resources
| map(values)
| length

As mentioned at jq: count nest object values which satisfy condition
the best solution (e.g. because it does not involve construction of an intermediate array) would be to use count, defined as follows:
def count(s): reduce s as $i (0; .+1);
With this definition, you should be able simply to wrap count(...) around your query since it produces a stream:
count(.modules[].resources | select (.[]!=null))
Or maybe you want something closer to:
count(.modules[].resources | objects)

Related

Updating an array element identified by other fields in the object using jq

Goal
I'd like to add a proxy-url field to the currently active clusters entry of my kubeconfig file. The "active" cluster is identified by the "active" context, which is itself identified by a top-level key current-context. Simplified, the JSON object looks something like:
{
"clusters":[
{
"name":"cluster1",
"field":"field1"
},
{
"name":"cluster2",
"field":"field2"
}
],
"contexts":[
{
"name":"context1",
"context": {
"cluster":"cluster1"
}
},
{
"name":"context2",
"context": {
"cluster":"cluster2"
}
}
],
"current-context": "context1"
}
And I'd like to update the clusters entry for cluster1 from:
{
"name":"cluster1",
"field":"field1"
}
to
{
"name":"cluster1",
"field":"field1",
"proxy-url":"my-url"
}
First attempt
jq '. as $o
| $o."current-context" as $current_context_name
| $o.contexts[] | select(.name == $current_context_name) as $context
| $o.clusters[] | select(.name == $context.context.cluster)
| .proxy_id |= "my-url"'
gives me
{
"name": "cluster1",
"field": "field1",
"proxy_id": "my-url"
}
-- great! But I need the rest of the object too.
Parentheses almost work
With parentheses, I can get the whole object back & add a "proxy-url" field to the active context, but I can't take it one step further to update the active cluster. This filter:
jq '(. as $o
| $o."current-context" as $current_context_name
| $o.contexts[] | select(.name == $current_context_name)
| ."proxy-url")
|= "my-url"'
works mint:
{
"clusters": [...], // omitted for brevity, unchanged
"contexts": [
{
"name": "context1",
"context": {
"cluster": "cluster1"
},
"proxy-url": "my-url" // tada!
},
{...} // omitted for brevity, unchanged
],
"current-context": "context1"
}
Trying to take it one step further (to update the cluster identified by that context, instead):
jq '(. as $o
| $o."current-context" as $current_context_name
| $o.contexts[] | select(.name == $current_context_name) as $context
| $o.clusters[] | select(.name == $context.context.cluster)
| ."proxy-url")
|= "my-url"'
gives me the following error:
jq: error (at <stdin>:26): Invalid path expression near attempt to access element "clusters" of {"clusters":[{"name":"clus...
exit status 5
How can I use the $context.context.cluster result to update the relevant clusters entry? I don't understand why this approach works for adding something to contexts but not to clusters.
Dirty solution
I can kludge together a new clusters entry & merge that with the top-level object:
jq '. as $o
| $o."current-context" as $current_context_name
| $o.contexts[] | select(.name == $current_context_name) as $context
| $o + {"clusters": [($o.clusters[] | select(.name == $context.context.cluster)."proxy-url" |= "my-url")]}
but this feels a bit fragile.
This solution retrieves the active cluster using an INDEX construction, then just sets the new field directly without modifying the context:
jq '
INDEX(.contexts[]; .name)[."current-context"].context.cluster as $cluster
| (.clusters[] | select(.name == $cluster))."proxy-url" = "my-url"
'
{
"clusters": [
{
"name": "cluster1",
"field": "field1",
"proxy-url": "my-url"
},
{
"name": "cluster2",
"field": "field2"
}
],
"contexts": [
{
"name": "context1",
"context": {
"cluster": "cluster1"
}
},
{
"name": "context2",
"context": {
"cluster": "cluster2"
}
}
],
"current-context": "context1"
}
Demo

jq: reduce and map combination

Here my current jq script:
def reduce_generateId:
.[] | (. + {generalPractitionerCode: (.UAB_UP + "-" + .UAB_COD_UAB)});
def reduce_generalPractitioner($practitionerRole):
(reduce $practitionerRole[] as $g (
{};
.[$g.oid1 + "-" + $g.oid2].generalPractitioner += ($g | [.id])
)) as $dict
| $dict;
reduce_generateId | reduce_generalPractitioner($generalPractitioner)
My jq command is:
jq -s -f merge-patient.jq --argfile generalPractitioner reduced_ids.json reduced_pacient.json
Where reduced_pacient.json:
{ "UAB_UP": "00003", "UAB_COD_UAB": "3212", "INVENTAT": "02"}
{ "UAB_UP": "00006", "UAB_COD_UAB": "5881", "INVENTAT": "102"}
{ "UAB_UP": "00006", "UAB_COD_UAB": "5751", "INVENTAT": "102"}
and reduced_ids.json:
[
{
"id": "3e67b455-8cdb-4bc0-a5e1-f90253870fc9",
"oid1": "04374",
"oid2": "INFP3"
},
{
"id": "0f22e5ff-70bc-457f-bdaf-7afe86d478de",
"oid1": "04376",
"oid2": "INF07"
}
]
As you can see, redux_generalPractitioner returns $dict straightforwardly. I'm getting this:
{
"04374-INFP3": {
"generalPractitioner": [
"3e67b455-8cdb-4bc0-a5e1-f90253870fc9"
]
},
"04376-INF07": {
"generalPractitioner": [
"0f22e5ff-70bc-457f-bdaf-7afe86d478de"
]
}
}
{
"04374-INFP3": {
"generalPractitioner": [
"3e67b455-8cdb-4bc0-a5e1-f90253870fc9"
]
},
"04376-INF07": {
"generalPractitioner": [
"0f22e5ff-70bc-457f-bdaf-7afe86d478de"
]
}
}
{
"04374-INFP3": {
"generalPractitioner": [
"3e67b455-8cdb-4bc0-a5e1-f90253870fc9"
]
},
"04376-INF07": {
"generalPractitioner": [
"0f22e5ff-70bc-457f-bdaf-7afe86d478de"
]
}
}
As you can see, I'm getting $dict three times.
I don't quite figure out why it's generating three $dict instead of one.
If I only perform reduce_generalPractitioner($generalPractitioner), I'm getting:
{
"04374-INFP3": {
"generalPractitioner": [
"3e67b455-8cdb-4bc0-a5e1-f90253870fc9"
]
},
"04376-INF07": {
"generalPractitioner": [
"0f22e5ff-70bc-457f-bdaf-7afe86d478de"
]
}
}
Any ideas?
Your program input, reduced_pacient.json is a stream of three objects. As you have set the --slurp flag, the initial context of your script is an array of three elements.
First, you're calling reduce_generateId which by iteration .[] decomposes the array into its three items. Thus reduce_generateId has three outputs.
Next, these are fed by pipe into your reduce_generalPractitioner function which consequently is run three times. Each run produces your $dict once, yielding three times in total.
Remember: Although your function reduce_generalPractitioner has an input parameter, it also has (just like every filter in jq) a general input context (addressed through piping), which determines the value of . and subsequently the number of runs.

aws dynamoDB cli - using jq on nested json

I need to swap a value on a dynamoDB entry. The difficulty is the coworker who put the table together used nested json on the field I need to edit. I'm having trouble figuring out to how to edit a value when the layout is asymmetrical.
Let's say a row in dynamoDB shows up like this with get-item:
{
"Item": {
"aws:rep:deleting": {
"BOOL": false
},
"service": {
"S": "g"
},
"settings": {
"L": [
{
"M": {
"fe_enabled": {
"BOOL": false
},
"stack_type": {
"S": "all"
},
"label": {
"S": "helm_chart_name"
},
"value": {
"S": "gate"
}
}
},
{
"M": {
"fe_enabled": {
"BOOL": true
},
"stack_type": {
"S": "all"
},
"label": {
"S": "helm_chart_version"
},
"value": {
"S": "0.1.1"
}
}
}
]
},
"label": {
"S": "gate"
},
"service_children": {
"L": []
},
"independent": {
"BOOL": true
}
}
}
I need to change helm_chart_version's value to - say - 0.1.2.
I've started by trying to isolate the value. I've tried variations of the following with no luck:
jq -r '.Item[].L.M | select(.label == "helm_4G_chart_version") | .value.S'
# errors because there are multiple L's so it's iterating over null
jq '..|.label? | select(type != "null")'
# discovered this neat syntax, but I can't figure out how to get value from that
jq --arg argName "argValue" '()' file.json | sponge file.json
# this is likely the "replace" solution, but I don't know what to put in the () to get the right value
Also open to hearing that I'm tackling this incorrectly - I'm a little out of my depth here. I'm planning to save the edited json to a file and then doing a put-item on it. Is this the right way to edit a dynamoDB entry?
If you want to traverse individual objects and find the object to update and set its value, you can do
jq '(.Item.settings.L[] | select(.M.label.S == "helm_chart_version")) |= (.M.value.S = "0.1.2")'
the part before the |= does the identification of the right object matching your string. Once the object is identified, the update operator |= just modifies the field with the desired value. See it working on jq-play
If convenience is at a premium, then using walk has much to recommend it:
walk(if type == "object" and .label.S == "helm_chart_version"
then .value.S = "0.1.2" else . end)
This of course assumes that "S" is known ahead of time. If that's not the case, then one possibility to consider would be:
walk(if type == "object" and has("label") and any(.label[]; . == "helm_chart_version")
then .value |= map_values("0.1.2") else . end)
Variations
If you don't want to walk the entire object, you could simply walk over .settings:
.Item.settings
|= walk(if type == "object"
and has("label")
and any(.label[]; . == "helm_chart_version")
then .value |= map_values("0.1.2")
else .
end)
Or if "label" is also an unknown:
.Item.settings
|= (. as $in
| [paths
| . as $p
| select(($in|getpath($p)) == "helm_chart_version")]
| reduce .[] as $p ($in; setpath($p | (.[-2] = "value"); "0.1.2")))

Accessing ancestors of array with given value

I can't seem to find the secret sauce to make jq do what I want.
Given the following contrived input:
{
"node1": {
"1": {
"Aliases": ["one", "uno"]
},
"2": {
"Aliases": ["two", "dos"]
}
},
"node2": {
"a": {
"Aliases": ["alpha"]
},
"b": {
"Aliases": ["bravo"]
}
}
}
I want to return the keys of the ancestors of Aliases when Aliases contains a particular value.
For example, given the search key dos, I want to return node1 and 2.
You can play with this data in jqplay. Any help would be appreciated.
(paths | select(.[-2] == "Aliases")) as $p
| select( "dos" == getpath($p))
| $p[:-2][]
Notice that there's no need for an additional variable (. as $in).

jq filter array of object unique property values

Very simple but beginning with jq.
What I have is an array of object. I want to have an array of object filtered by unique value 'myprop'
[
{
"myProp": "similarValue"
},
{
"myProp": "similarValue"
},
{
"myProp": "OtherValue"
}
]
Result I want:
[
{
"myProp": "similarValue"
},
{
"myProp": "OtherValue"
}
]
What I've tried:
.someContainerProp | unique[] .myProp
The problem is that is returns just the list of values not list of object
It was pretty easy actually
.values | unique_by(.myProp)

Resources