How to merge keys in inner object with its parent (unnesting) - jq

This is an example input:
{
"key1": "value",
"key2": [
{"key3": 5, "key4": "value1"},
{"key3": null, "key4": "value2"},
{"key3": 9, "key4": "value3"}
]
}
Example output:
{
"key1": "value",
"value1": 5,
"value2": null,
"value3": 9
}
This was generated with the following Python code:
new = {x['key4']: x['key3'] for x in old}
new['key1'] = old['key1']
I attempted to do this with jq, but this is far as I got:
[.[] | {k1: .key1, k2: .key2 | map({ (.key4): .key3}) | add}]
Which gives me
{
"key1": "value",
"key2": {
"value1": 5,
"value2": null,
"value3": 9
}
}
I'd say that what's missing is how to "merge" keys in the object with the top-level. How can I do that?

With your input JSON fixed to take keys/values in double-quotes, your expression could be written as
[ {key1}, (.key2[] | { (.key4): .key3} ) ] | add

Related

Using jq to conditionally flatten recursive json

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

Parsing json file with jq from HPE iLO

I have a json file pulled from an HPE iLO interface with the snmp configuration. It looks like:
[
{
"Comments": {
"BIOSDate": "01/23/2021",
"BIOSFamily": "U30",
"Manufacturer": "HPE",
"Model": "ProLiant DL380 Gen10",
"SerialNumber": "5UNESX378",
"iLOVersion": "iLO 5 v2.65"
}
},
{
"#HpeiLOSnmpService.v2_3_0.HpeiLOSnmpService": {
"/redfish/v1/Managers/1/SnmpService/": {
"#odata.context": "/redfish/v1/$metadata#HpeiLOSnmpService.HpeIloSnmpService",
"#odata.id": "/redfish/v1/Managers/1/SnmpService",
"Actions": {
"#HpeIloSnmpService.SendSNMPTestAlert": {
"target": "/redfish/v1/Managers/1/SnmpService/Actions/HpeILOSnmpService.SendSNMPTestAlert/"
}
},
"AlertDestinationAssociations": [
{
"SNMPAlertProtocol": "SNMPv3Trap",
"SecurityName": null
}
],
"AlertDestinations": [
"1.2.3.4",
"5.6.7.8",
null,
null
],
"AlertsEnabled": true,
"Name": "SnmpService"
},
"PeriodicHSATrapConfig": "Disabled",
"ReadCommunities": [
"",
"",
""
],
"Role": "",
"RoleDetail": "",
"SNMPAlertDestinations": {
"#odata.id": "/redfish/v1/Managers/1/SnmpService/SNMPAlertDestinations/"
},
"SNMPUsers": {
"#odata.id": "/redfish/v1/Managers/1/SnmpService/SNMPUsers/"
},
"SNMPv1Enabled": false,
"SNMPv3EngineID": "0x8920000000E3028329E002033",
"SNMPv3InformRetryAttempt": 2,
"SNMPv3InformRetryIntervalSeconds": 15,
"Status": {
"State": "Enabled"
},
"TrapCommunities": [
"",
"",
"",
"",
"",
"",
""
],
"TrapSourceHostname": "Manager",
"Users": [
{
"AuthProtocol": "MD5",
"PrivacyProtocol": "DES",
"SecurityName": "",
"UserEngineID": null
},
{
"AuthProtocol": "MD5",
"PrivacyProtocol": "DES",
"SecurityName": "",
"UserEngineID": null
},
{
"AuthProtocol": "SHA",
"PrivacyProtocol": "AES",
"SecurityName": "oneview_4849283d97929392",
"UserEngineID": null
},
{
"AuthProtocol": "MD5",
"PrivacyProtocol": "DES",
"SecurityName": "",
"UserEngineID": null
}
]
}
}
]
I want to select an element in the Users array that has SecurityName set to "" and change that element. I don't need the Comments portion. So, I try to select the section starting with #HpeiLOSnmpService.v2_3_0.HpeiLOSnmpService with:
jq -r '.[] | .#HpeiLOSnmpService.v2_3_0.HpeiLOSnmpService' snmp.json
but it gives me everything without the enclosing array. Anyone have a suggestion?
Thanks!
# starts a comment and your jq program degenerates to .[]|. which is identical to the program .[] (|. is a no-op/the identity filter). This program will simply select all values from the input.
You must quote certain characters, such as #, when they are part of propery names. The following will work with your JSON file:
jq -r '.[] | ."#HpeiLOSnmpService".v2_3_0.HpeiLOSnmpService'
Thanks. Using the quotes around the key with a '#' works. Ultimately, selecting the SecurityName that was unset was done with:
jq -r '.Users[] | select (.SecurityName == "") | {"AuthProtocol":.AuthProtocol,"PrivacyProtocol":.PrivacyProtocol,"SecurityName":.SecurityName,"UserEngineID":.UserEngineID}'

Replace values inside list

I have the following object :
{
"db_credentials": {
"database": "greengrass",
"host": "localhost",
"password": "yZqXJzXHLUsLlPm",
"port": 7086,
"username": "greengrass"
},
"default_interval": 90000,
"fields_selected": [
{
"measurement": "ABPLCGD-GD_AB1AirFlowCalc",
"aggregation": "last",
"step": "10m",
"timeserie_physical": "null",
"timeserie_type": "null",
"timeserie_interpolation": "null",
"timeserie_unit": "null",
"timeserie_step": "10"
},
{
"measurement": "ABPLCGD-GD_AB1InletAirTempAct",
"aggregation": "last",
"step": "10m",
"timeserie_physical": "null",
"timeserie_type": "null",
"timeserie_interpolation": "null",
"timeserie_unit": "null",
"timeserie_step": "10"
}
]
}
and i wish to transform it into :
{
"db_credentials": {
"database": "greengrass",
"host": "localhost",
"password": "yZqXJzXHLUsLlPm",
"port": 7086,
"username": "greengrass"
},
"default_interval": 90000,
"fields_selected": [
{
"name": "ABPLCGD-GD_AB1AirFlowCalc",
"aggregation": {
"step": "10m",
"function": "last"
}
},
{
"name": "ABPLCGD-GD_AB1InletAirTempAct",
"aggregation": {
"step": "10m",
"function": "last"
}
}
]
}
i have tried multiple solution but this is the maximum where i can get :
jq ' .fields_selected = .fields_selected | map({name : .measurement , aggregation : {step: .step , function: .aggregation}})' config-it-client.json
but i got always this error that Cannot index number with string "measurement" and i cant figure out what i'm doing wrong
I'd use the following:
.fields_selected[] |= { name: .measurement, aggregation: { function: .aggregation, step } }
To get it working, you were just missing parens.
.fields_selected = .fields_selected | ...
means
( .fields_selected = .fields_selected ) | ...
It should be
.fields_selected = ( .fields_selected | ... )
This gives us
.fields_selected = (
.fields_selected |
map({
name: .measurement,
aggregation: {
function: .aggregation,
step: .step
}
})
)
Demo on jqplay
But we can improve this. foo = ( foo | ... ) can generally be written as foo |= ( ... ).
.fields_selected |= map({
name: .measurement,
aggregation: {
function: .aggregation,
step: .step
}
})
We could modify the objects in the fields array instead of the array itself.
.fields_selected[] |= {
name: .measurement,
aggregation: {
function: .aggregation,
step: .step
}
}
Finally, { foo: .foo, ... } can be shortened to { foo, ... }.
.fields_selected[] |= {
name: .measurement,
aggregation: {
function: .aggregation,
step
}
}
As a sh one-liner:
jq '.fields_selected[] |= { name: .measurement, aggregation: { function: .aggregation, step } }'
Demo on jqplay
I was able to figure out what i was doing wrong:
jq '.fields_selected = (.fields_selected | map({name : .measurement , aggregation : {step: .step , function: .aggregation}}))' config-it-client.json

Using a key within a map

I have this function:
def getMap(value = null) {
[
"SomeTitle": [ Param: 9, Size: 2, Default: 150, Val: value ]
]
}
and I can use getMap(152).SomeTitle.Val
What I want to do is use the key Size while calculating the Key Val, something like:
def getMap(value = null) {
[
"SomeTitle": [ Param: 9, Size: 2, Default: 150, Val: value * Size ]
]
}
Is there a way to use the Value of a Key within a map as a variable while calculating the Value of a another Key
This in Java/Groovy
Groovy can't autoreference the map on it's initialization, but you can use a with {} method to do some post-initialization processing:
def getMap(value = null) {
[
"SomeTitle": [ Param: 9, Size: 2, Default: 150 ].with {
put('Val', value * get('Size'))
it
}
]
}
assert getMap(10).SomeTitle.Val == 20

jq: Getting two levels of keys

I have some json data that looks like:
{
"p": {
"d": {
"a" : {
"r": "foo",
"g": 1
},
"b": {
"r": "bar",
"g": 2
}
},
"c": {
"e": {
"r": "baz",
"g": 1
}
},
...
}
}
I want something like:
{
"d": [
"a",
"b"
],
"c": [
"e"
]
}
I can get the list of keys on the first level under "p" with jq '.p|keys', and the structure and keys on the second level with jq '.p|map(.|keys)', but I can't figure out how to combine it.
Use map_values instead of map to map the values of a JSON object while preserving the keys:
jq '.p | map_values(keys)'
On jq versions lower than 1.5, map_values is not defined: instead, you can use []|=:
jq '.p | . []|= keys'
In general
Top level keys:
curl -s https://crates.io/api/v1/crates/atty | jq '. |= keys'
[
"categories",
"crate",
"keywords",
"versions"
]
Two levels of keys:
curl -s https://crates.io/api/v1/crates/atty | jq '.| map_values(keys)'
{
"crate": [
"badges",
"categories",
"created_at",
"description",
"documentation",
"downloads",
"exact_match",
"homepage",
"id",
"keywords",
"links",
"max_version",
"name",
"newest_version",
"recent_downloads",
"repository",
"updated_at",
"versions"
],
"versions": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16
],
"keywords": [
0,
1,
2
],
"categories": []
}
Method versions
topLevelJsonKeys() {
curl -s $1 | jq '. |= keys'
# EXAMPLE:
# topLevelJsonKeys https://crates.io/api/v1/crates/atty
}
topLevelJsonKeys2() {
curl -s $1 | jq '.| map_values(keys)'
# EXAMPLE:
# topLevelJsonKeys2 https://crates.io/api/v1/crates/atty
}
Here is a solution which uses reduce and setpath
.p
| reduce keys[] as $k (
.
; setpath([$k]; .[$k] | keys)
)

Resources