jq split string and assign - json

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)

Related

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

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

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) ))

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: how to only update data if existing?

With dev version of jq, this could be done with jq '.x.y |= if . then 123 else empty end'. (Because bug #13134 is solved.)
How can I do this in jq 1.5?
example:
in {"x": {"y": 5}}, y should be changed to 123,
but in {"x": {"z": 9}}, nothing should change.
do you need to use |=? If not could you use ordinary assignment? e.g.
jq -Mnc '
{"x": {"y": 5}} | if .x.y != null then .x.y = 123 else . end
, {"x": {"z": 9}} | if .x.y != null then .x.y = 123 else . end
'
output
{"x":{"y":123}}
{"x":{"z":9}}
With built-in has() function:
jq -nc '{"x":{"y": 5}} | if (.x | has("y")) then .x.y=123 else empty end'
The output:
{"x":{"y":123}}
Both the following produce the desired results (whether using 1.5 or later), but there are important differences in the semantics (having to do with the difference between {"x": null} and {}):
if has("x") and (.x | has("y")) then .x.y = 123 else . end
if .x.y? then .x.y = 123 else . end
Using streams could actually handle this quite nicely. A stream for an object will yield paths and values to actual existing values in your input. So search for the pairs that contain your path and update the value while rebuilding the stream.
$ jq --argjson path '["x","y"]' --argjson new '123' '
fromstream(tostream|select(length == 2 and .[0] == $path)[1] = $new)
' input.json

JQ JSON select with dynamic shell param

I use JQ for JSON formatting and filtering the data.
sed "s/.*Service - //p" tes.log | jq 'if (.requests | length) != 0 then .requests |= map(select(.orderId == "2260")) else "NO" end' > ~/result.log
Here, the orderid is been hardcoded to 2260. But my requirement is to make it parameter driven.
So I store the param to a variable called ORDER_ID like,
ORDER_ID=2260
and then using $ORDER_ID, but it doesnt work.
sed "s/.*Service - //p" tes.log | jq 'if (.requests | length) != 0 then .requests |= map(select(.orderId == "$ORDER_ID")) else "NO" end' > ~/result.log
It is not replacing the $ORDER_ID with the passed param.
Thanks
The shell does not expand variables inside single quotes. If jq is agnostic about the quote type, try swapping double and single quotes throughout. Also, if you want to select only the line with Service - in it, you need the -n flag for sed:
sed -n 's/.*Service - //p' tes.log | jq "if (.requests | length) != 0 then .requests |= map(select(.orderId == '$ORDER_ID')) else 'NO' end" > ~/result.log
As the following bash script demonstrates, Stephen Gildea's example will not work because jq does not allow use of ' single quotes.
#!/bin/bash
ORDER_ID=2260
jq "if (.requests | length) != 0 then .requests |= map(select(.orderId == '$ORDER_ID')) else 'NO' end" <<EOF
{"requests":[{"orderId":2260}]}
EOF
Specifically the 'NO' literal will generate a syntax error:
jq: error: syntax error, unexpected INVALID_CHARACTER (Unix shell quoting issues?) at <top-level>, line 1:
if (.requests | length) != 0 then .requests |= map(select(.orderId == '2260')) else 'NO' end
jq: error: Possibly unterminated 'if' statement at <top-level>, line 1:
if (.requests | length) != 0 then .requests |= map(select(.orderId == '2260')) else 'NO' end
jq: 2 compile errors
One approach which will work is to use the --argjson option, use single quotes for the entire jq filter and double quotes for strings literals within the filter. E.g.
jq --argjson ORDER_ID "$ORDER_ID" '
if (.requests|length)!=0 then .requests |= map(select(.orderId == $ORDER_ID)) else "NO" end
'