Adding Element to array of element in json using jq - arrays

Ok, i spent way to much time on this.
Using jq, i want to test if an element exist in an array, if yes being able to mofify this element, if not adding this element to that array
I have read
Update one value in array of dicts, using jq
How to check if element exists in array with jq
Which gave hints But i am stuck
Let say i have a json file like :
{
"LOGIN":"user",
"COPY": [
{
"CHAINLIST":"chain1",
"ELEMENT": [ "element1-1","element1-2" ]
},
{
"CHAINLIST":"chain2",
"ELEMENT": [ "element2-1","element2-2" ]
}
]
}
I Would like to add elements to the COPY array based in the CHAINLIST id , let say those 3 elements :
//ELEM A
{
"CHAINLIST":"chain3",
"ELEMENT": [ "element3-1" ]
}
//ELEM B
{
"CHAINLIST":"chain2",
"ELEMENT": [ "element2-1","element2-3" ]
}
//ELEM C
{
"CHAINLIST":"chain1",
"ELEMENT": [ "element1-1","element1-2" ]
}
ELEM A has the CHAINLIST id chain3 who doesn't exit in the COPY Array _> let's add it
ELEM B Is already present in the COPY aray, I want to update the ELEMENT array
content with the new value
ELEM C Already exist and is up to date, i don't need to do anything.
In my example the final json object would be
{
"LOGIN":"user",
"COPY": [
{
"CHAINLIST":"chain1",
"ELEMENT": [ "element1-1","element1-2" ]
},
{
"CHAINLIST":"chain2",
"ELEMENT": [ "element2-1","element2-2","element2-3" ]
},
{
"CHAINLIST":"chain3",
"ELEMENT": [ "element3-1"]
}
]
}
I am using jq The procedure to add the element Elem to the object Obj would be :
Elem.CHAINLIST exist in Obj.COPY [] ?
yes : add Elem.ELEMENT to Obj.ELEMENT[] with unique to qvoid duplicates
no : add Elem to Obj.COPY[]
The best i get is
(if .COPY[]? | select (.CHAINLIST=="chain3") == "" then . else .COPY[.COPY | length ] |= . + { "CHAINLIST":"chain3","ELEMENT":[ "element3-1"]} end )
But select (.CHAINLIST=="chain3") == "" return nothing I i can figure out how to test if empty.
Thank you for your time !

The following solution uses the built-in function INDEX/2 to create convenient and efficient representations
of the collections of CHAINLIST-ELEMENT objects, together with a bespoke function, augment/1, for combining elements.
It is assumed that the objects defining the updates are presented as an array in the file edits.json.
< input.json jq --argfile edits edits.json '
# Use `unique` to combine the .ELEMENT fields
def augment($b):
reduce ($b|keys_unsorted[]) as $key (.;
.[$key].ELEMENT = (.[$key].ELEMENT + $b[$key].ELEMENT | unique));
(INDEX($edits[];.CHAINLIST) | map_values(del(.CHAINLIST))) as $edits
| (INDEX(.COPY[]; .CHAINLIST) | map_values(del(.CHAINLIST))) as $COPY
| .COPY |= ($COPY
| augment($edits)
| to_entries
| map( { CHAINLIST: .key, ELEMENT: .value.ELEMENT} ))
Output: as specified in the question.
Note: if the objects defining the updates are presented as a sequence of objects in a file, you could use the above solution with --slurpfile instead of --argfile.

Related

Changing array of numbers into array of objects jq

I'm totally brand new to JSON and jq so this might seem like a simple question.
I'd like to change an array of numbers into an array of objects with a key for each value (number).
Let's say I have a JSON file like this:
{
"foo": [1519739200, 1519739600, 1519740000]
}
Then my desired output would be:
{
"foo": [
{
"id": 1519739200
},
{
"id": 1519739600
},
{
"id": 1519740000
},
]
}
So far everything I've seen was connected with adding new keys with values to existing object or merging two arrays into few objects. I know that I can add more keys into already existing object but how can I add keys to an array? I assume I have to change array elements into objects first but how do I do it?
Thank you for answer.
Check this,
https://jqplay.org/s/xF9DyVbhXD
{ foo : [ { id : .foo[] } ] }
There are a few ways to go about it.
First you want to create a new object for each item in foo:
$ jq -c '{ id: .foo[] }'
{"id":1519739200}
{"id":1519739600}
{"id":1519740000}
You can then rebuild the "shape" you had - first with [ ... ]
$ jq -c '[ { id: .foo[] } ]'
[{"id":1519739200},{"id":1519739600},{"id":1519740000}]
Then the { foo: }
$ jq -c '{ foo: [ { id: .foo[] } ] }'
{"foo":[{"id":1519739200},{"id":1519739600},{"id":1519740000}]}
Another option is to use |= to modify/update .foo directly.
$ jq -c '.foo |= [{id: .[]}]'
{"foo":[{"id":1519739200},{"id":1519739600},{"id":1519740000}]}

PowerShell Array, adding new records where one property is an array

I am unable to use ArrayList or avoid using += for array manipulation. Wishing that powerShell had a universal add or append available.
I have the below JSON array for $aksAppRules.RulesText
[{
"Name": "A2B",
"Description": null,
"SourceAddresses": [
"10.124.176.0/21",
"10.124.184.0/21"
],
"TargetFqdns": [
"*.github.com",
"*.grafana.com",
"*.trafficmanager.net",
"*.loganalytics.io",
"*.applicationinsights.io",
"*.azurecr.io",
"*.debian.org"
],
"FqdnTags": [],
"Protocols": [
{
"ProtocolType": "Https",
"Port": 443
}
],
"SourceIpGroups": []
},
{
"Name": "Y2office365",
"Description": null,
"SourceAddresses": [
"10.124.176.0/21",
"10.124.184.0/21"
],
"TargetFqdns": [
"smtp.office365.com"
],
"FqdnTags": [],
"Protocols": [
{
"ProtocolType": "Http",
"Port": 25
},
{
"ProtocolType": "Http",
"Port": 587
}
],
"SourceIpGroups": []
}
]
I managed to make this work with the below powershell snippet
$new_list = #()
$collectionRules = $aksAppRules.RulesText | ConvertFrom-Json
foreach ($rule in $collectionRules) {
$protoArray = #()
ForEach ($protocol in $rule.Protocols) {
$protoArray += $protocol.ProtocolType + "," + $protocol.Port
}
#$new_list += , #($rule.Name, $rule.SourceAddresses, $rule.TargetFqdns, $protoArray )
# the 'comma' right after += in below line tells powershell to add new record.
$new_list += , #{Name=$rule.Name;SourceAddresses=$rule.SourceAddresses; TargetFqdns=$rule.TargetFqdns;Protocol=$protoArray}
}
$new_list | ConvertTo-Json | ConvertFrom-Json | select Name, SourceAddresses, TargetFqdns, Protocol| Convert-OutputForCSV -OutputPropertyType Comma | Export-Csv .\test.csv
The CSV looks like
I am unable to do this using Arraylists and without using += as I heard it is inefficient with large arrays.
I have to copy things to a new array because I have to change the key:value format of the original "Protocols" to a 2 d array.
Any pointers will be appreciated.
Yes, you should avoid using the increase assignment operator (+=) to create a collection as it exponential expensive. Instead you should use the pipeline
collectionRules = $aksAppRules.RulesText | ConvertFrom-Json
foreach ($rule in $collectionRules) {
[pscustomobject]#{
Name = $rule.Name
SourceAddresses = $rule.SourceAddresses
TargetFqdns = $rule.TargetFqdns
Protocol = #(
ForEach ($protocol in $rule.Protocols) {
$protocol.ProtocolType + "," + $protocol.Port
}
)
}
} | Convert-OutputForCSV -OutputPropertyType Comma | Export-Csv .\test.csv
Note 1: I have no clue why you are doing | ConvertTo-Json | ConvertFrom-Json, so far I can see there is no need for this if you use a [pscustomobject] rather than a [Hashtabe] type.
Note 2: I no clue what the function Convert-OutputForCSV is doing and suspect that isn't required either (but left it in).

Using jq to produce one line of output per array element in a json object that also includes other fields from the json object

I'm trying to format some data that consists of json objects that includes some identifying information along with an array of one or more json objects, I would like the result to be one line of data per array element where each line should include some fields from the array element and some fields from the identifying information.
My sample data is below:
{
"eventCreation": {
"timeStamp": "2020-06-06T15:07:20Z",
"epoch": 1591456040
},
"eventData": {
"applName": "SampleApp",
"channelName": "SYSTEM.DEF.SVRCONN",
"connectionName": "127.0.0.1",
"channelType": "Svrconn",
"remoteProduct": "MQJM",
"remoteVersion": "09010005",
"activityTrace": [
{
"operationId": "Get",
"operationTime": "11:07:18",
"qmgrOpDuration": 102,
"reasonCode": {
"name": "No Msg Available",
"value": 2033
},
"objectName": "SYSTEM.DEFAULT.LOCAL.QUEUE"
},
{
"operationId": "Cb",
"operationTime": "11:07:18",
"qmgrOpDuration": 10,
"reasonCode": {
"name": "None",
"value": 0
},
"objectName": "SYSTEM.DEFAULT.LOCAL.QUEUE"
},
{
"operationId": "Cb",
"operationTime": "11:07:18",
"qmgrOpDuration": 12,
"reasonCode": {
"name": "None",
"value": 0
},
"objectName": "SYSTEM.DEFAULT.LOCAL.QUEUE"
}
]
}
}
I would like to get an output like this:
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","11:07:18","Get",102,"SYSTEM.DEFAULT.LOCAL.QUEUE",2033
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","11:07:18","Cb",10,"SYSTEM.DEFAULT.LOCAL.QUEUE",0
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","11:07:18","Cb",12,"SYSTEM.DEFAULT.LOCAL.QUEUE",0
I can pick any one element of the array and get it to print 3 lines, but if I add a 2nd element it will print 9 lines, 3rd element prints 27, etc.
For example:
jq -r '{channelName: .eventData.channelName, channelType: .eventData.channelType, connectionName: .eventData.connectionName, applName: .eventData.applName, remoteProduct: .eventData.remoteProduct, remoteVersion: .eventData.remoteVersion, operationId: .eventData.activityTrace[].operationId}|[.[]]|#csv' TEST.json
Will produce this:
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Get"
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Cb"
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Cb"
If I add a second like this:
jq -r '{channelName: .eventData.channelName, channelType: .eventData.channelType, connectionName: .eventData.connectionName, applName: .eventData.applName, remoteProduct: .eventData.remoteProduct, remoteVersion: .eventData.remoteVersion, operationId: .eventData.activityTrace[].operationId, qmgrOpDuration: .eventData.activityTrace[].qmgrOpDuration}|[.[]]|#csv' TEST.json
Will produce this:
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Get",102
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Get",10
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Get",12
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Cb",102
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Cb",10
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Cb",12
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Cb",102
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Cb",10
"SYSTEM.DEF.SVRCONN","Svrconn","127.0.0.1","SampleApp","MQJM","09010005","Cb",12
Using your approach, the following is a solution:
.eventData
| ({channelName, channelType, connectionName, applName, remoteProduct, remoteVersion}
+ ( .activityTrace[]
| { operationTime, operationId, qmgrOpDuration, objectName, v: .reasonCode.value}))
| [.[]]
| #csv
The key is to iterate just once.
Notice also that this solution achieves its brevity in part by using the fact that {foo: .foo} can be abbreviated to {foo}.
A slightly more efficient approach
.eventData
| [.channelName, .channelType, .connectionName, .applName, .remoteProduct, .remoteVersion]
+ ( .activityTrace[]
| [.operationTime, .operationId, .qmgrOpDuration, .objectName, .reasonCode.value] )
| #csv
.operationTime first
.eventData
| [.channelName, .channelType, .connectionName, .applName, .remoteProduct, .remoteVersion] as $x
| ( .activityTrace[]
| [.operationTime]
+ $x
+ [.operationId, .qmgrOpDuration, .objectName, .reasonCode.value] )
| #csv

Delete objects and arrays with jq which match a key

I have a JSON with the following content:
{
"data": [
{
"name": "Test",
"program": {
"publicAccess": "--------",
"externalAccess": false,
"userGroupAccesses": [
{
"access": "r-------"
},
{
"access": "rw------"
}
],
"id": "MmBqeMLIC2r"
},
"publicAccess": "rw------"
}
]
}
And I want to delete all keys (recursively) which match publicAccess or userGroupAccesses, so that my JSON looks like this:
{
"data": [
{
"name": "Test",
"program": {
"externalAccess": false,
"id": "MmBqeMLIC2r"
}
}
]
}
I've copied jq's builtin walk function from source.
# Apply f to composite entities recursively, and to atoms
def walk(f):
. as $in
| if type == "object" then
reduce keys[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
# My Code
walk(if (type == "object" and .publicAccess)
then del(.publicAccess)
elif (type == "array" and .userGroupAccesses)
then del(.userGroupAccesses)
else
.
end )
Gives me jq: error (at <stdin>:2622): Cannot index array with string "userGroupAccesses". Also if I use .userGroupAccesses[] - How do I get the result?
Snippet on jqplay: https://jqplay.org/s/1m7wAeHMTu
Your problem is when type == "array" is true . will be an array so .userGroupAccesses won't work. What you want to do is focus on the case when . is an object. In your call to walk you only need to check for type == "object" and then remove the members you don't want. e.g.
walk(if type == "object" then del(.publicAccess, .userGroupAccesses) else . end)
Try it online at jqplay.org
You can also solve this without walk by using Recursive Descent .. e.g.
del(.. | .publicAccess?, .userGroupAccesses?)
Try it online at jqplay.org

Can't get jq to recognize an array within a json file

I have a json file that I use for work that I need to parse that is in the following format:
(^)#(^)#(^)#(^)bminter#ubuntu:~$ cat jqtest
{
"files":[
{
"BLOCK1":{
"SUBBLOCK1":{
"akey1":"avalue1",
"bkey1":"bvalue1",
"ckey1":"cvalue1"
},
"dkey1":"dvalue1",
"key":"evalue1"
}
},
{
"BLOCK-2":{
"SUBBLOCK2":{
"akey2":"avalue2",
"bkey2":"bvalue2"
},
"ckey2":"cvalue2",
"key":"dvalue2"
}
},
{
"BLOCK-A":{
"SUBBLOCK2":{
"akey2":"avalue2",
"bkey2":"bvalue2"
},
"ckey2":"cvalue2",
"key":"dvalue2"
}
}],
"NOBLOCK":"value",
"key":"NOBLOCKvalue"
}
So it's an array nested within a json file. jq .[] jqtest gives me everything in the file. Even the data outside the array. Except, outside the array, I'm only given the values not the keys:
(^)#(^)#(^)#(^)bminter#ubuntu:~$ jq .[] jqtest
[
{
"BLOCK1": {
"SUBBLOCK1": {
"akey1": "avalue1",
"bkey1": "bvalue1",
"ckey1": "cvalue1"
},
"dkey1": "dvalue1",
"key": "evalue1"
}
},
{
"BLOCK-2": {
"SUBBLOCK2": {
"akey2": "avalue2",
"bkey2": "bvalue2"
},
"ckey2": "cvalue2",
"key": "dvalue2"
}
},
{
"BLOCK-A": {
"SUBBLOCK2": {
"akey2": "avalue2",
"bkey2": "bvalue2"
},
"ckey2": "cvalue2",
"key": "dvalue2"
}
}
]
"value"
"NOBLOCKvalue"
(^)#(^)#(^)#(^)bminter#ubuntu:~$
Beyond that I can't access any block inside the array:
(^)#(^)#(^)#(^)bminter#ubuntu:~$ jq '.[].BLOCK1' jqtest
jq: error (at jqtest:36): Cannot index array with string "BLOCK1"
(^)#(^)#(^)#(^)bminter#ubuntu:~$ jq '.[].BLOCK-2' jqtest
jq: error (at jqtest:36): Cannot index array with string "BLOCK"
(^)#(^)#(^)#(^)bminter#ubuntu:~$ jq '.[].BLOCK-A' jqtest
jq: error: A/0 is not defined at <top-level>, line 1:
.[].BLOCK-A
jq: 1 compile error
(^)#(^)#(^)#(^)bminter#ubuntu:~$
How do I access the array?
The array of objects with non-uniform keys is making things a little tricky here. Once you've gotten past .files you need to start using Array Iteration [] to access those elements and then use object operations like keys to go deeper.
Here is a function which may help in this situation. It scans .files for an object with a key matching the specified key and then returns the corresponding value:
def getfile($k): .files[] | select(keys[] | .==$k) | .[$k];
If jqtest contains the sample data the command
$ jq -M '
def getfile($k): .files[] | select(keys[] | .==$k) | .[$k];
getfile("BLOCK1").SUBBLOCK1.akey1
' jqtest
Returns
"avalue1"
Another approach is to use a function to convert .files[] into a more useful form. e.g.
$ jq -M '
def files: reduce .files[] as $f ({}; ($f|keys[0]) as $k | .[$k] = $f[$k]) ;
files
' jqtest
this returns a more uniform structure without arrays
{
"BLOCK1": {
"SUBBLOCK1": {
"akey1": "avalue1",
"bkey1": "bvalue1",
"ckey1": "cvalue1"
},
"dkey1": "dvalue1",
"key": "evalue1"
},
"BLOCK-2": ...
so with it you can write
files.BLOCK1.SUBBLOCK1
to obtain
{
"akey1": "avalue1",
"bkey1": "bvalue1",
"ckey1": "cvalue1"
}
Note that jq will re-evaluate the files function with each use so the following form may be more practical:
files as $files
| $files.BLOCK1.SUBBLOCK1
If you find this representation useful you may want to skip the function and instead just start your filter with
.files = reduce .files[] as $f ({}; ($f|keys[0]) as $k | .[$k] = $f[$k])
e.g.
$ jq -M '
.files = reduce .files[] as $f ({}; ($f|keys[0]) as $k | .[$k] = $f[$k])
# more stuff goes here
' jqtest
which converts your input to
{
"files": {
"BLOCK1": {
"SUBBLOCK1": {
"akey1": "avalue1",
"bkey1": "bvalue1",
"ckey1": "cvalue1"
},
"dkey1": "dvalue1",
"key": "evalue1"
},
"BLOCK-2": {
"SUBBLOCK2": {
"akey2": "avalue2",
"bkey2": "bvalue2"
},
"ckey2": "cvalue2",
"key": "dvalue2"
},
"BLOCK-A": {
"SUBBLOCK2": {
"akey2": "avalue2",
"bkey2": "bvalue2"
},
"ckey2": "cvalue2",
"key": "dvalue2"
}
},
"NOBLOCK": "value",
"key": "NOBLOCKvalue"
}
making whatever else you need to do after that easier

Resources