jq: pick value if field exist - jq

This is my script:
def pick_partOf($uids):
(reduce $uids[] as $uid (
{};
. + ($uid | {(.oid1): .id})
)) as $dict
| map(. + { partOf: $dict[."parent-identifier-value"] } | del(..|nulls));
def pick_organization:
{
resourceType: "Organization",
active: true,
partOf: .partOf?
};
pick_partOf($identifiers) | pick_organization
Problem here, is that sometimes some input objects doesn't contains ."parent-identifier-value", and I'm getting:
jq: error (at rsan.json:7): Cannot index object with null
Those are my input objects:
{
"identifier-value": "61",
"name": "name61"
}
{
"id": "62",
"name": "name62",
"parent-identifier-value": "61"
}
Currently, I'm launching jq using empty array as $identifiers arg:
jq -s -f build-organization-bundle.jq --argjson identifiers '[]' rsan.json
However, my identifiers is an other json files like this:
{
"id": "be8a02b6-54b9-450f-bf45-f28ab7ebf2dd",
"oid1": "61"
}
{
"id": "f1652f3a-bca9-433c-a2c4-c924811196f6",
"oid1": "62"
}
I've tried with:
def pick_partOf($uids):
(reduce $uids[] as $uid (
{};
. + ($uid | {(.oid1): .id})
)) as $dict
| map(. + (
if (.|has("parent-identifier-value")) then
{ partOf: $dict[."parent-identifier-value"] }
else null end
))
| del(..|nulls));
But here I'm facing with some syntax issue I don't quite figure out how to fix it.

You did not specify why you want pick_partOf to achieve.
If its output should be
{
"identifier-value": "61",
"name": "name61"
}
{
"id": "62",
"name": "name62",
"parent-identifier-value": "61",
"partOf": "be8a02b6-54b9-450f-bf45-f28ab7ebf2dd"
}
then you can use
if ."parent-identifier-value" then
.partOf = $dict[."parent-identifier-value"]
else
.
end
Demo on jqplay
Repeating ."parent-identifier-value" was annoying me.
."parent-identifier-value" as $piv |
if $piv then .partOf = $dict[$piv] else . end
Demo on jqplay

Related

Cannot index array with string "xxx"

I have a json file as this,
# cat input.json
{
"foo":[
"key1",
"key2"
],
"bar": "key3"
}
And I want to join all those strings ("key1", "key2", key3") into one string, and I define the filter as this,
# cat filter.jq
[.[] | . as { foo: $names, bar: $name} | {
name1: [ $names | range(0;length) as $i | {
key1: ($names[$i])
}],
name2: {
key2: $name
}
} | {
values: (.name1 | map(.key1) | join(" ") + .name2.key2)
}]
But this doesn't work,
# <input.json jq --slurp --from-file filter.jq > output.json
jq: error (at <stdin>:8): Cannot index array with string "name2"
What's wrong going here?
btw. concat with a string seems works.
values: (.name1 | map(.key1) | join(" ") + "key3")
The problem is underspecified, but you might wish to consider:
[.. | strings] | join(" ")
or similar.

jq: where select clause is applied

Here my documents:
[
{
"id":"f3b8c257-9950-45e7-9e79-ace19ec8905e",
"identifier":[
{
"system":{
"value":"urn:oid:2.16.724.4.9.10.2"
},
"value":{
"value":"10839812"
}
}
]
},
{
"id":"f0a1e3ae-826f-4a03-b29e-10ef3bc86ea0",
"identifier":null
}
]
Currently, I'm aplying this jq filter:
map(
{
id,
dni: .identifier[] | (select(.system.value == "urn:oid:1.3.6.1.4.1.19126.3") | .value.value)
}
)
However, I'm getting this message:
jq: error (at practitioner-mongoexport.json:146715): Cannot iterate over null (null)
As you can guess, problem arises processing second object where .identifier: null.
I've tried with that:
map(
{
id,
dni: select(.identifier) | .identifier[] | (select(.system.value == "urn:oid:1.3.6.1.4.1.19126.3") | .value.value)
}
)
I've also tried with:
map(
select(.identifier) |
{
id,
dni: .identifier[] | (select(.system.value == "urn:oid:1.3.6.1.4.1.19126.3") | .value.value)
}
)
Then result is [].
I don't quite figure out what I'm doing wrong.
My desired output would be:
[
{
"id":"f3b8c257-9950-45e7-9e79-ace19ec8905e",
"dni": "10839812"
}
]
Any ideas?
You need to deal with the case if .identifier is null. Here, the ? operator may help.
without selection
jq 'map({id, dni: (.identifier[]?).value.value})'
Demo
with selection
jq 'map({id, dni: (.identifier[]? | select(.system.value == "urn:oid:2.16.724.4.9.10.2")).value.value})'
Demo
[
{
"id": "f3b8c257-9950-45e7-9e79-ace19ec8905e",
"dni": "10839812"
}
]
map(
select(.identifier?[]?.system.value == "urn:oid:2.16.724.4.9.10.2")
| { id, "dni": .identifier[].value.value }
)
Will generate:
[
{
"id": "f3b8c257-9950-45e7-9e79-ace19ec8905e",
"dni": "10839812"
}
]
The trick here is .identifier?[]?.system.value where the ? will ignore .identifier if it's null
JqPlay Demo

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

JQ Cross reference or how to replace one value with another part of the input

I want to parse terraform.tfstate (where openstack provider is used), to return instance name and it's internal + floating IP (if assigned).
First select what we are interested in:
jq -r '.modules?[]|.resources[]?|select(.type == "openstack_compute_floatingip_v2", .type == "openstack_compute_instance_v2")' < terraform.tfstate
For simplicity, pre-parsed example with the above part (one FIP and one instance):
{
"type": "openstack_compute_floatingip_v2",
"depends_on": [
"openstack_networking_router_interface_v2.management"
],
"primary": {
"id": "48b039fc-a9fa-4672-934a-32d6d267f280",
"attributes": {
"address": "209.66.89.143",
"fixed_ip": "10.10.10.5",
"id": "48b039fc-a9fa-4672-934a-32d6d267f280",
"instance_id": "597e75e8-834d-4f05-8408-e2e6e733577e",
"pool": "public",
"region": "RegionOne"
},
"meta": {},
"tainted": false
},
"deposed": [],
"provider": "provider.openstack"
}
{
"type": "openstack_compute_instance_v2",
"depends_on": [
"openstack_compute_floatingip_v2.management",
"openstack_compute_secgroup_v2.ssh_only",
"openstack_networking_network_v2.management"
],
"primary": {
"id": "597e75e8-834d-4f05-8408-e2e6e733577e",
"attributes": {
"access_ip_v4": "10.10.10.5",
"access_ip_v6": "",
"all_metadata.%": "1",
"all_metadata.habitat": "sup",
"availability_zone": "nova",
"flavor_id": "eb36e84e-17c1-42ab-b359-4380f6f524ae",
"flavor_name": "m1.large",
"force_delete": "false",
"id": "597e75e8-834d-4f05-8408-e2e6e733577e",
"image_id": "c574aeed-e47c-4fb7-9da0-75550b76ee56",
"image_name": "ubuntu-16.04",
"key_pair": "vault-etcd_test_tf",
"metadata.%": "1",
"metadata.habitat": "sup",
"name": "ctl01",
"network.#": "1",
"network.0.access_network": "false",
"network.0.fixed_ip_v4": "10.10.10.5",
"network.0.fixed_ip_v6": "",
"network.0.floating_ip": "",
"network.0.mac": "02:c6:61:f9:ee:7e",
"network.0.name": "management",
"network.0.port": "",
"network.0.uuid": "f2468669-e321-4eb4-9ede-003e362a8988",
"region": "RegionOne",
"security_groups.#": "1",
"security_groups.1845949017": "vault-etcd_test_ssh_only",
"stop_before_destroy": "false"
},
"meta": {
"e2bfb730-ecaa-11e6-8f88-34363bc7c4c0": {
"create": 1800000000000,
"delete": 1800000000000,
"update": 1800000000000
}
},
"tainted": false
},
"deposed": [],
"provider": "provider.openstack"
}
Required is to take from "type": "openstack_compute_floatingip_v2" replace .primary.attributes.address and .fixed_ip and from corresponding .instance_id the .name.
So, sth like:
{"address": "209.66.89.143",
"fixed_ip": "10.10.10.5",
"name": "ctl01"}
Well, I came with an idea while using walk, but miss how to actually assign the proper value from corresponding instance id:
jq -r "$(cat floating.jq)" terraform.tfstate
floating.jq:
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;
.modules?[]|.resources[]?|select(.type ==
"openstack_compute_floatingip_v2", .type ==
"openstack_compute_instance_v2")|
.primary|walk( if type == "object" and .attributes.address then
.attributes.instance_id |= "REFERRED VALUE HERE") else . end)
Let's assume the two related objects are in a file named two.json. Then one way to merge the information from both objects is using the -s command-line option, e.g.
jq -s '
(.[0].primary.attributes | {address, fixed_ip})
+ {name: .[1].primary.attributes.name}' two.json
Output
With your example input, the output would be:
{
"address": "209.66.89.143",
"fixed_ip": "10.10.10.5",
"name": "ctl01"
}

Interaction between {} and select

Here's my test data:
[
{
"id": "id-1",
"tags": {
"key": "name",
"value": "name-1"
}
},
{
"id": "id-2"
}
]
I'm trying to simplify the output, to show the 'name' field if present, and always show the id. For example, this script almost works:
~ $ cat testdata | jq '.[] | {id, name: .tags.value}'
{
"id": "id-1",
"name": "name-1"
}
{
"id": "id-2",
"name": null
}
When I try to add in a guard against .keys not existing and filter for the section of 'keys' I care about, here's what happens:
~ $ cat testdata | jq '.[] | {id, name: (select(.tags.key == "name") | .tags.value)}'
{
"id": "id-1",
"name": "name-1"
}
I assume {} is somehow ending up with a zero-length array instead of 'null'. What should I be using instead of |? What am I misunderstanding?
I ended up solving the problem using: [POSSIBLY_MATCHED_EXPRESSION][0], in this case:
cat testdata | jq '.[] | {id, name: ([select(.tags.key == "name") | .tags.value][0])}'
If I'm understanding correctly, if you wanted to include a name only if it existed, I'd do this:
map({id} + with_entries(select(.key == "tags") | .value))
Otherwise if you don't mind null names:
map({id, name: with_entries(select(.key == "tags") | .value) | .name})
Here's a more general solution if you have other "tags" so it's not hardcoded to only accept name values.
This assumes that any object value is actually a key/value pair.
map(with_entries(if .value | type == "object" then .value else . end))
Or if tags is the only dynamic property:
map(with_entries(if .key == "tags" then .value else . end))
If the goal is to produce:
{"id":"id-1","name":"name-1"}
{"id":"id-2"}
then the following three expressions are essentially equivalent solutions:
.[] | if .tags.key == "name" then {id, name: .tags.value} else {id} end
.[] | {id} + (if .tags.key == "name" then {name: .tags.value} else {} end)
.[] | (select(.tags.key == "name") | {id, name: .tags.value}) // {id}
You could just add
| if .name == null then del(.name) else . end
to the end of your filter to get rid of the .name key when its value is null.
With your test data, the following
.[]
| {id, name:.tags.value }
| if .name == null then del(.name) else . end
produces
{
"id": "id-1",
"name": "name-1"
}
{
"id": "id-2"
}

Resources