How to print out the top-level json after modification of descendants - json

Hello i managed to create this jq filter .profiles | recurse | .gameDir? | if type == "null" then "" else . end | scan("{REPLACE}.*") | sub("{REPLACE}"; "{REPLACESTRINGHERE}"). it succesfully replaces what i want (checked at jqplay.org) but now i'd like to print the full json and not just the modified strings

Adapting your query:
.profiles |= walk( if type == "object" and has("gameDir")
then .gameDir |=
(if type == "null" then "" else . end
| scan("{REPLACE}.*") | sub("{REPLACE}"; "{REPLACESTRINGHERE}"))
else .
end )
(This can easily be tweaked for greater efficiency.)
If your jq does not have walk, you can google it (jq “def walk”) or snarf its def from the jq FAQ https://github.com/stedolan/jq/wiki/FAQ
walk-free approach
For the record, here's an illustration of a walk-free approach using paths. The following also makes some changes in the computation of the replacement string -- notably it eliminates the use of scan -- so it is not logically equivalent, but is likely to be more useful as well as more efficient.
.profiles |=
( . as $in
| reduce (paths | select(.[-1] == "gameDir")) as $path ($in;
($in | getpath($path)
| if type == "null" then ""
else sub(".*{REPLACE}"; "{REPLACESTRINGHERE}")
end) as $value
| setpath($path; $value) ))

Related

Difference between `null` and `no output`

I've encountered some difference between null and nothing, can somebody explain it? As in most languages null is considered/used to represent nothing.
The select is documented to return no output. And adding(ie. +) null to X yields X. Now consider these demonstrative examples(takes no input):
adding nothing
here we have empty object, which we update with nothing:
{} | . |= . + ({} | select (.foo == 123))
which results in
null
adding null
same template but with alternative operator to substitute nothing to null:
{} | . |= . + ({} | select (.foo == 123)//null)
which results in
{}
Can someone explain the difference nothing vs null?
null is just a regular JSON value; and conceptually, it is totally different from the absence of a value, i.e, what you termed nothing. Take a look at these for example (empty is a filter that returns nothing):
$ jq -n '[null] | length'
1
$ jq -n '[empty] | length'
0
That {} + null returns {} back, and that {} | . |= empty does exactly what del(.) does are merely design choices.

jq: error (at ec-state:1028): Cannot iterate over null (null)

I have a lengthy JSON file and I execute the command to get the output shown below:
jq -s '.[]
| ."lrouter/show"[]
| del( . | select(.type == "TUNNEL-VRF"))
| del(.ports[] | select(.type == "blackhole" or .type == "cpu-port" or .type == "loopback"))
| "Name: \(.name)" ,
"UUID: \(.uuid)" ,
(.ports[] | {Port_Name: .name,
Port_Type: .type,
Port_Peer: .peer,
Port_IPs: .ips[],
Port_Admin_Up: .admin_up,
Port_Op_State: .op_state_up } )' ec-state
"Name: SR-t0-uplink"
"UUID: 23354d26-6994-46d9-b78c-bb565a1c13f2"
{
"Port_Name": "uplink",
"Port_Type": "uplink",
"Port_Peer": "d78089f6-71b5-4c8e-a477-69ee01f17c5c",
"Port_IPs": "1.1.13.5/24",
"Port_Admin_Up": true,
"Port_Op_State": true
}
{
"Port_Name": "bp-sr0-port",
"Port_Type": "backplane",
"Port_Peer": null,
"Port_IPs": "169.254.0.2/28",
"Port_Admin_Up": false,
"Port_Op_State": false
}
jq: error (at ec-state:1028): Cannot iterate over null (null)
I get the desired result however, I also get the jq error at the end of the result. Just curious to know what am I doing incorrectly with the query.
Since your input is large, you might consider adding assertions or equivalent. Since your program evidently expects arrays at various points, you could instrument it with a function such as:
def q($n; $msg):
if type == "array" or type == "object"
then .
else error("\($msg): composite expected # \($n) vs \(.)")
end;
Your program could then be instrumented as follows:
range(0;length) as $n
| .[$n]
| ."lrouter/show" | q($n; 2) | .[]
| del( . | select(.type == "TUNNEL-VRF"))
| del(.ports | q($n; 3) | .[] | select(.type == "blackhole" or .type == "cpu-port" or .type == "loopback"))
| "Name: \(.name)" ,
"UUID: \(.uuid)" ,
(.ports[] | {Port_Name: .name,
Port_Type: .type,
Port_Peer: .peer,
Port_IPs: (.ips | q($n; 4) |.[]),
Port_Admin_Up: .admin_up,
Port_Op_State: .op_state_up } )

jq split string and assign

I have the following json
{
"version" : "0.1.2",
"basePath" : "/"
}
and the desired output is
{
"version" : "0.1.2",
"basePath" : "beta1"
}
I have the following jq which is producing the error below:
.basePath = .version | split(".") as $version | if $version[0] == "0" then "beta"+ $version[1] else $version[0] end
jq: error (at :3): split input and separator must be strings
exit status 5
Using .basePath = .version assigns the value successfully and .version | split(".") as $version | if $version[0] == "0" then "beta"+ $version[1] else $version[0] end on its own returns "beta1". Is there a way to assign the string to the basePath key?
Good news! Your proposed solution is just missing a pair of parentheses. Also, there is no need for $version. That is, this will do it:
.basePath = (.version | split(".")
| if .[0] == "0" then "beta"+ .[1] else .[0] end)

How to make paths to leafs of a JSON?

Say we have the following JSON:
[
{
"dir-1": [
"file-1.1",
"file-1.2"
]
},
"dir-1",
{
"dir-2": [
"file-2.1"
]
}
]
And we want to get the next output:
"dir-1/file-1.1"
"dir-1/file-1.2"
"dir-1"
"dir-2/file-2.1"
i.e. to get the paths to all leafs, joining items with /. Is there a way to do that on JQ?
I tried something like this:
cat source-file | jq 'path(..) | [ .[] | tostring ] | join("/")'
But it doesn't produce what I need even close.
You could take advantage of how streams work by merging the path with their values. Streams will only emit path, value pairs for leaf values. Just ignore the numbered indices.
$ jq --stream '
select(length == 2) | [(.[0][] | select(strings)), .[1]] | join("/")
' source-file
returns:
"dir-1/file-1.1"
"dir-1/file-1.2"
"dir-1"
"dir-2/file-2.1"
Here is a solution similar to Jeff Mercado's which uses tostream and flatten
tostream | select(length==2) | .[0] |= map(strings) | flatten | join("/")
Try it online at jqplay.org
Another way is to use a recursive function to walk the input such as
def slashpaths($p):
def concat($p;$k): if $p=="" then $k else "\($p)/\($k)" end;
if type=="array" then .[] | slashpaths($p)
elif type=="object" then
keys_unsorted[] as $k
| .[$k] | slashpaths(concat($p;$k))
else concat($p;.) end;
slashpaths("")
Try it online at tio.run!
Using --stream is good but the following is perhaps less esoteric:
paths(scalars) as $p
| getpath($p) as $v
| ($p | map(strings) + [$v])
| join("/")
(If using jq 1.4 or earlier, and if any of the leaves might be numeric or boolean or null, then [$v] above should be replaced by [$v|tostring].)
Whether the result should be regarded as "paths to leaves" is another matter...

Jq: recursively delete all keys that match a given pattern

How to recursively delete all keys that match a given pattern?
I have following jq config, but it doesn't seem to work:
walk( if (type == "object" and (.[] | test('.*'))) then del(.) else . end)
A robust way (with respect to different jq versions) to delete all keys matching a pattern (say PATTERN) would be to use the idiom:
with_entries(select( .key | test(PATTERN) | not))
Plugging this into walk/1 yields:
walk(if type == "object" then with_entries(select(.key | test(PATTERN) | not)) else . end)