JQ applying function to dict but print surrounding datas - jq

I'd like
"1": {
"id": "1",
"type": "select",
"label": "country :",
"choices": {
"1": {
"label": "Canada CAN",
"value": "",
},
"2": {
"label": "United States USA",
"value": ""
}
}
}
to produce
"1": {
"id": "1",
"type": "select",
"label": "country :",
"choices": {
"1": {
"label": "Canada",
"value": "CAN",
},
"2": {
"label": "United States",
"value": "USA"
}
}
}
By now I have a two-step solution consisting of a sed-like function
def do_extract:
if .value | test("^$") then
(.value = (.label | capture(".* (?<code>...)")).code) | .label = (.label | capture("(?<name>.*) ...$").name)
else
.
end;
and a direct access to the sub-tree []."1"."choices"|keys_unsorted as $key|map_values(do_extract) but I have to manually copy-paste the output in place of the original "choices" dict.
Is there a way to do the function but still print the surrounding datas ?
Thanks

Well to get your desired result, you could do this:
.[].choices[] |= (.label | capture("^(?<label>.*?) (?<value>[^ ]+)$"))
To do that within your function, I'd change it to this:
def do_extract:
if .value == "" then
(.label | capture("^(?<label>.*?) (?<value>[^ ]+)$"))
else
.
end;
Then use it:
.[].choices[] |= do_extract

Related

JSONPath cannot query if condition is a child's property of array

I have a json as below. I want to select list reference of item which has method is "method A". But I cannot query with operator == (with Jayway JSONPath evaluation)
$.items[*].[?(#.methods[*].name == 'method A')].reference
But when I replace operator as contains, the jsonpath works.
$.items[*].[?(#.methods[*].name contains 'method A')].reference
I don't know why operator == does not work. Could anyone help me explain the reason?
JSON:
{
"name": "List codes",
"items": [
{
"name": "Cash",
"reference": "CA",
"methods": [
{
"name": "method A",
"id": "3543"
},
{
"name": "method B",
"id": "3544"
}
]
},
{
"name": "Debt",
"reference": "DE",
"methods": [
{
"name": "method C",
"id": "3545"
},
{
"name": "method B",
"id": "3544"
}
]
},
{
"name": "Property",
"reference": "PR",
"methods": [
{
"name": "method C",
"id": "3545"
},
{
"name": "method A",
"id": "3543"
}
]
}
]
}
#.methods[*].name will yield multiple results resulting into an array. You cannot use == to compare list of values with a string.
if you need to use == operator then you have to use the index along with ||
$.items[?(#.methods[0].name == 'method A' || #.methods[1].name == 'method A')].reference

Cannot access child value on Newtonsoft.Json.Linq.JProperty error

I have this json format I'm making an API using ASP.NET.
{
"0": {
"order_id": 11748,
"complete_date": "2021-04-19 14:48:41",
"shipping_code": "aramex.aramex",
"awbs": [
{
"aramex_id": "1314",
"order_id": "11748",
"awb_number": "46572146154",
"reference_number": "11748",
"date_added": "2021-03-04 03:46:58"
}
],
"payment": {
"method": {
"name": "الدفع عند الاستلام",
"code": "cod"
},
"invoice": [
{
"code": "sub_total",
"value": "120.8700",
"value_string": "120.8700 SAR",
"title": "الاجمالي"
},
{
"code": "shipping",
"value": "0.0000",
"value_string": "0.0000 SAR",
"title": "ارمكس"
},
{
"code": "coupon",
"value": "-13.9000",
"value_string": "-13.9000 SAR",
"title": "قسيمة التخفيض(RMP425)"
},
{
"code": "cashon_delivery_fee",
"value": "5.0000",
"value_string": "5.0000 SAR",
"title": "رسوم الدفع عند الاستلام"
},
{
"code": "tax",
"value": "18.1300",
"value_string": "18.1300 SAR",
"title": " ضريبة القيمة المضافة (15%)"
},
{
"code": "total",
"value": "130.1000",
"value_string": "130.1000 SAR",
"title": "الاجمالي النهائي"
}
]
},
"product": [
{
"id": 69,
"name": "مخلط 4 أو دو بيرفيوم للجنسين - 100 مل",
"sku": "45678643230",
"weight": "0.50000000",
"quantity": 1,
"productDiscount": "",
"images": []
}
]
}
}
How can I reach order_id? I made an object let's say its name is obj1 I tried foreach obj1 and storing into a variable obj1.order_id;
It stored null in the variable. the {"0"} is the numbering of orders starts 0-1-2 etc.
You can deserialize that json to Dictionary<string,dynamic> without creating a new class as following:
var values = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(json);
var orderId = values["0"]["order_id"].ToString();
This will give you 11748 as a result.

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"
}

Filtering objects in jq by existence of nested array values

Given a document like this:
[
{
"KVs" : [
{
"Key": "animal",
"Value": "lion"
},
{
"Key": "mascot",
"Value": "lion"
}
],
"name": "roger"
},
{
"KVs" : [
{
"Key": "animal",
"Value": "zebra"
},
{
"Key": "mascot",
"Value": "lion"
}
],
"name": "linda"
}
]
I want to use jq to select only those elements of the top array that contain the KV pair animal == "lion".
The output for the above JSON document should be:
{
"KVs" : [
{
"Key": "animal",
"Value": "lion"
},
{
"Key": "mascot",
"Value": "lion"
}
],
"name": "roger"
}
Can't figure out how to accomplish this with select(). I know how to use it to select based on one specific field. e.g. by key name: .[] | select(.KVs[].Key == "animal"), right? But how do I tell it to match the same KV object on two fields (Key & Value)?
NM, solved it with the help of jqplay and some trial and error.
This is the solution:
.[] | select(.KVs[] | .Key == "animal" and .Value == "lion")
(jqplay permalink)

Using jq, how can I limit values based on a key

For an input file that looks like this:
{
"employees": [
{
"number": "101",
"tags": [
{
"value": "yes",
"key": "management"
},
{
"value": "joe",
"key": "login"
},
{
"value": "joe blogs",
"key": "name"
}
]
},
{
"number": "102",
"tags": [
{
"value": "no",
"key": "management"
},
{
"value": "jane",
"key": "login"
},
{
"value": "jane doe",
"key": "name"
}
]
},
{
"number": "103",
"tags": [
{
"value": "no",
"key": "management"
},
{
"value": "john",
"key": "login"
},
{
"value": "john doe",
"key": "name"
}
]
}
]
}
... I'd like to get details for all non-management employees so that the desired output looks like this:
{
"number": "102",
"name": "jane doe",
"login": "jane"
}
{
"number": "103",
"name": "john doe",
"login": "john"
}
I can't figure out how to limit results based on a key without selecting that key (in this case "management")
The following is a slightly more succinct solution:
.employees[]
| .tags |= from_entries
| select(.tags.management == "no")
| {number, "name": .tags.name, "login": .tags.login}
Using from_entries, this worked for me:
$ jq '.employees[] | {number: .number, tags: .tags | from_entries} | select(.tags.management=="no") | {number: .number, name: .tags.name, login: .tags.login}' input
... and the output is:
{
"number": "102",
"name": "jane blogs",
"login": "jane"
}
{
"number": "103",
"name": "john doe",
"login": "john"
}
There may be a better way to achieve what I wanted, so I'll leave the question open for a while if someone wants to offer a better solution.
Here is another solution which uses from_entries
.employees[]
| {number} + (.tags | from_entries)
| if .management == "no" then {number, name, login} else empty end

Resources