Recursively replace value with $path in JQ - json

Assuming that i have a complex JsonObject
{
"parent": {
"name": "value",
"child": {
"child_value1": "value",
"child_value2": "value",
"child_value3": ["value1","value2"],
"child_value4": {
"child_child_value1":"value"
}
}
}
}
I want to replace the value of each key, with the name of key prefixed with $
{
"parent": {
"name": "$name",
"child": {
"child_value1": "$child_child_value1",
"child_value2": "$child_child_value2",
"child_value3": ["$child_child_value3_0","$child_child_value3_1"],
"child_value4": {
"child_child_value1":"$child_child_value4_child_child_value1"
}
}
}
}
Is there a way to do it recursively?
EDIT
This is the currently configuration file that I am using
{
"apis": {
"order": {
"base_url": "$mapping_base_url"
},
"payment": {
"base_url": "$admin_base_url"
}
},
"features": {
"authentication": {
"authProviders": true,
"registration": false
}
},
"availableLocales":["en","es"]
}
This is the result using the recomended jq expression:
. |= reduce paths(strings) as $p (.; setpath($p; "$" + ($p | join("_"))))
{
"apis": {
"order": {
"base_url": "$apis_order_base_url"
},
"payment": {
"base_url": "$apis_payment_base_url"
}
},
"features": {
"authentication": {
"authProviders": true,
"registration": false
}
},
"availableLocales": [
"$availableLocales_0",
"$availableLocales_1"
]
}

You're looking for something like this:
.parent |=
reduce paths(strings) as $p (.;
setpath($p; "$" + ($p | join("_")))
)
Online demo

Related

jq - remove non-matching fields in "object-array-with-objects"

Given the following JSON-object:
{
"meta": {
"data1": {
"keep": { "key": "value" }
}
},
"detail": {
"data2": [
{
"keep1": "keep1value",
"keep2": "keep2value",
"nokeep1": "abc"
}
],
"data3": [
{
"keep1": "keep1value",
"keep2": "keep2value",
"nokeep2": { "abc": "def" }
}
]
},
"drop" : "this"
}
I'm trying to clean it by removing unwanted fields, like "remove", "nokeep1" and "nokeep2".
However objects in the "data2" and "data3" arrays might contain more fields than the example "nokeepX", but will always contain "keep1" and "keep2" which I want to keep.
My desired output is the following JSON:
{
"meta": { "data1": { "keep": { "key": "value" } } },
"detail": {
"data2": [
{
"keep1": "keep1value",
"keep2": "keep2value"
}
],
"data3": [
{
"keep1": "keep1value",
"keep2": "keep2value"
}
]
}
}
I've managed to remove the "drop" field with this query:
jq 'def pick($paths): . as $root | reduce ($paths[]|[.]|flatten(1)) as $path ({}; . + setpath($path; $root|getpath($path))); pick([["meta"], ["detail", "data2"], ["detail", "data3"]])'
However I've been struggling to figure out how to remove the "nokeepX" fields - is it possible to accomplish this?
If you have only a limited set of properties, it could be easier not to remove unwanted fields, but create the output from the required fields only:
{
meta,
detail: .detail | {
data2: .data2 | map({ keep1, keep2 }),
data3: .data3 | map({ keep1, keep2 })
}
}
Output:
{
"meta": {
"data1": {
"keep": {
"key": "value"
}
}
},
"detail": {
"data2": [
{
"keep1": "keep1value",
"keep2": "keep2value"
}
],
"data3": [
{
"keep1": "keep1value",
"keep2": "keep2value"
}
]
}
}
The approach can be combined with dropping certain fields:
{
meta,
detail: .detail | {
data2: .data2 | map(del(.nokeep1)),
data3: .data3 | map(del(.nokeep2))
}
}
producing the same output as above.
Just provide all the concrete paths to del:
del(
.detail.data2[0].nokeep1,
.detail.data3[0].nokeep2,
.drop
)
Demo
Or generalize by e.g. traversing all array items (not just the first) using [] without indices:
del(
.detail.data2[].nokeep1,
.detail.data3[].nokeep2,
.drop
)
Demo
Or go arbitrarily deep using .., and just provide the deepest field names:
del(.. | objects | .nokeep1, .nokeep2, .drop)
Demo
Output:
{
"meta": {
"data1": {
"keep": "true"
}
},
"detail": {
"data2": [
{
"keep1": "keep1value",
"keep2": "keep2value"
}
],
"data3": [
{
"keep1": "keep1value",
"keep2": "keep2value"
}
]
}
}
For the other way round, you could list all the leaf paths using paths(scalars), filter out those where the deepest level .[-1] does not match your criteria, and use delpaths to remove the remaining leafs:
delpaths([paths(scalars) | select(
.[-1] | IN("keep", "keep1", "keep2") | not
)])
Demo

Filter nested JSON object using JSONPath with Condition

I have a below json object and need to get only c_Internet child object with a condition where the PIID is having value "1234567".
{
"OrderComponentInfo": {
"members": {
"ADD": {
"DIA": [
{
"c_Internet": {
"PIID": "1234567",
"IPv6_Multiple_Path_Needed": ""
},
"Test": {
"PIID": "1234567",
"INT_Acc_MSP_ID": "",
"ConnectedService": "INTERNET"
}
},
{
"c_Internet": {
"PIID": "7658964",
"IPv6_Multiple_Path_Needed": ""
},
"Test": {
"PIID": "7658964",
"INT_Acc_MSP_ID": "",
"ConnectedService": "INTERNET"
}
}
]
}
}
}
}
I trued below query but dint work
$..ADD.DIA[?(#.PIID = "1234567")][['c_Internet']]
Thanks in advance.
you have to use c_Internet.PIID not just PIID
const id="1234567";
$..ADD.DIA[?( #.c_Internet.PIID == id )]["c_Internet"]
result
[
{
"PIID": "1234567",
"IPv6_Multiple_Path_Needed": ""
}
]

jq: insert{"key": "value"} pair in the nested structure

I just wonder how we can achieve this with simple online jq.
AS_IS
{
"rules": {
"aaa": {
"xxx": {
"url": "https://microsoft.com"
},
"xxxx": {
"url": "https://netflix.com"
}
},
"bbb": {
"xxx": {
"url": "https://amazon.com"
}
},
"ccc": {
"xxx": {
"url": "https://google.com"
}
}
}
}
TO_BE
{
"rules": {
"aaa": {
"xxx": {
"url": "https://microsoft.com"
},
"xxxx": {
"url": "https://netflix.com"
}
},
"bbb": {
"xxx": {
"url": "https://amazon.com"
}
},
"ccc": {
"xxx": {
"url": "https://google.com",
"pass": "abc"
}
}
}
}
What I tried so far is
.rules[] | select(.xxx.url | try contains("google.com")) | .xxx += {"pass": "abc"}
and the output is below, and I successfully insert "pass": "abc".
{
"xxx": {
"url": "https://google.com",
"pass": "abc"
}
}
but I want to get the whole lines of code. (Not a part of it)
ref. https://jqplay.org/s/S_UWrrLRl5
The idea is right, but ensure the last update happens without traversing down to the leaf path (at the same level as xxx), but relative to the root path by simply wrapping the filter under (..)
( .rules[] | select(.xxx.url | try contains("google.com")) | .xxx ) += {"pass": "abc"}
Always prefer using exact match conditions instead of a partial match
( .rules[] | select(.xxx.url == "https://google.com") | .xxx ) += {"pass": "abc"}
Here's an even more "dynamic" approach:
walk(if type=="object"
and (.url|type)=="string"
and (.url|endswith("google.com"))
then .pass = "abc" else . end)
Of course, depending on the task requirements, a simpler solution such as the following might suffice:
.rules.ccc.xxx.pass = "abc"

Reconstructing JSON with jq

I have a JSON like this (sample.json):
{
"sheet1": [
{
"hostname": "sv001",
"role": "web",
"ip1": "172.17.0.3"
},
{
"hostname": "sv002",
"role": "web",
"ip1": "172.17.0.4"
},
{
"hostname": "sv003",
"role": "db",
"ip1": "172.17.0.5",
"ip2": "172.18.0.5"
}
],
"sheet2": [
{
"hostname": "sv004",
"role": "web",
"ip1": "172.17.0.6"
},
{
"hostname": "sv005",
"role": "db",
"ip1": "172.17.0.7"
},
{
"hostname": "vsv006",
"role": "db",
"ip1": "172.17.0.8"
}
],
"sheet3": []
}
I want to extract data like this:
sheet1
jq '(something command)' sample.json
{
"web": {
"hosts": [
"172.17.0.3",
"172.17.0.4"
]
},
"db": {
"hosts": [
"172.17.0.5"
]
}
}
Is it possible to perform the reconstruction with jq map?
(I will reuse the result for ansible inventory.)
Here's a short, straight-forward and efficient solution -- efficient in part because it avoids group_by by courtesy of the following generic helper function:
def add_by(f;g): reduce .[] as $x ({}; .[$x|f] += [$x|g]);
.sheet1
| add_by(.role; .ip1)
| map_values( {hosts: .} )
Output
This produces the required output:
{
"web": {
"hosts": [
"172.17.0.3",
"172.17.0.4"
]
},
"db": {
"hosts": [
"172.17.0.5"
]
}
}
If the goal is to regroup the ips by their roles within each sheet you could do this:
map_values(
reduce group_by(.role)[] as $g ({};
.[$g[0].role].hosts = [$g[] | del(.hostname, .role)[]]
)
)
Which produces something like this:
{
"sheet1": {
"db": {
"hosts": [
"172.17.0.5",
"172.18.0.5"
]
},
"web": {
"hosts": [
"172.17.0.3",
"172.17.0.4"
]
}
},
"sheet2": {
"db": {
"hosts": [
"172.17.0.7",
"172.17.0.8"
]
},
"web": {
"hosts": [
"172.17.0.6"
]
}
},
"sheet3": {}
}
https://jqplay.org/s/3VpRc5l4_m
If you want to flatten all to a single object keeping only unique ips, you can keep everything mostly the same, you'll just need to flatten the inputs prior to grouping and remove the map_values/1 call.
$ jq -n '
reduce ([inputs[][]] | group_by(.role)[]) as $g ({};
.[$g[0].role].hosts = ([$g[] | del(.hostname, .role)[]] | unique)
)
'
{
"db": {
"hosts": [
"172.17.0.5",
"172.17.0.7",
"172.17.0.8",
"172.18.0.5"
]
},
"web": {
"hosts": [
"172.17.0.3",
"172.17.0.4",
"172.17.0.6"
]
}
}
https://jqplay.org/s/ZGj1wC8hU3

Merging two files using jq

I've got two files.
First:
[
{ "person1": [] },
{ "person2": [] }
]
Second:
[
{
"person2": { "attribute1": "wer", "attribute2": "sdf" }
},
{
"person2": { "attribute1": "ert", "attribute2": "dfg" }
},
{
"person2": { "attribute1": "rty", "attribute2": "fgh" }
},
{
"person3": { "attribute1": "tyu", "attribute2": "ghj" }
},
{
"person1": { "attribute1": "yui", "attribute2": "hjk" }
}
]
I try to merge them, using jq. For each person from the first file (in the second file migth be more persons, which should be ignored) create list of it's attributes. So an output should look something like this:
[
{
"person1":
[
{ "attribute1": "yui", "attribute2": "hjk" }
]
},
{
"person2":
[
{ "attribute1": "wer", "attribute2": "sdf" },
{ "attribute1": "ert", "attribute2": "dfg" },
{ "attribute1": "rty", "attribute2": "fgh" }
]
}
]
I tried different options, but I can't achieve expected result.
jq 'reduce (input[]|to_entries[]) as $e (add;
if has($e.key) then .[$e.key] += [$e.value] else . end
) | [keys_unsorted[] as $k|{($k): .[$k]}]' file1 file2
online demo
The following solution is focused on efficiency, and also suggests an alternative format for the output:
jq program (merge.jq)
add as $dict
# aggregation:
| (reduce input[] as $record ({};
($record|keys_unsorted[0]) as $person
| if $dict[$person] then .[$person] += [$record[$person]] else . end )) as $answer
# re-arrangement
| reduce ($dict|keys_unsorted[]) as $person ([]; . + [ {($person): $answer[$person] } ] )
Invocation
jq -f merge.jq first.json second.json