I am trying to slice an array in jq where the end index is passed as an argument from the shell (bash):
end_index=7
cat obj.json | jq --arg eidx $end_index, '.arr[0:$eidx]'
This works as expected when the index is hard-coded
cat obj.json | jq '.arr[0:7]'
but in the example at the top, I get an error message
jq: error (at <stdin>:0): Start and end indices of an array slice must be numbers
I suspect this might be to do with how jq handles variable substitution within the slice operator [:], but none of my attempts to solve the problem, e.g. by enclosing the variable name in curly braces .arr[0:${eidx}], has worked.
You can convert a string to a number using tonumber, as in:
jq --arg eidx 1 '.arr[0:($eidx|tonumber)]'
If your jq is sufficiently recent, you can use --argjson instead of --arg:
jq --argjson eidx 1 '.arr[0:$eidx]'
When you pass an argument via --arg it is treated as a string, not an int:
--arg name value:
This option passes a value to the jq program as a predefined variable. If you run jq with --arg foo bar, then $foo is available in the program and has the value "bar". Note that value will be treated as a string, so --arg foo 123 will bind $foo to "123".
From the docs (emphases added)
so it would seem that you cannot use --arg to pass a value to be used in a slice. In this example, you could just use the shell expansion though:
jq ".arr[0:$end_index]" obj.json
the double quotes will have the shell expand your variable before passing it to jq (though other expansions will happen to, so make sure you mean them to happen.
Related
I would like to write a simple jq file that allows me to count items grouped by a specified key.
I expect the script contents to be something similar too:
group_by($group) | map({group: $group, cnt: length})
and to invoke it something like
cat my.json | jq --from-file count_by.jq --args group .header.messageType
Whatever I've tried the argument always ends up as a string and is not usable as a key.
Since you have not followed the minimal complete verifiable example
guidelines, it's a bit difficult to know what the best approach to your problem will be, but whatever approach you take, it is important to bear in mind that --arg always passes in a JSON string. It cannot be used to pass in a jq program fragment unless the fragment is a JSON string.
So let's consider one option: passing in a JSON object representing a path that you can use in your program.
So the invocation could be:
jq -f count_by.jq --argjson group '["header", "messageType"]'
and the program would begin with:
group_by(getpath($group)) | ...
Having your cake ...
If you really want to pass in arguments such as .header.messageType, there is a way: convert the string $group into a jq path:
($group|split(".")|map(select(length>0))) as $path
So your jq filter would look like this:
($group|split(".")|map(select(length>0))) as $path
| group_by(getpath($path)) | map({group: $group, cnt: length})
Shell string interpolation
If you want a quick bash solution that comes with many caveats:
group=".header.messageType"
jq 'group_by('"$group"') | map({group: "'"$group"'", cnt: length}'
I have a json object with numeric keys in an example.json file:
{
"1": "foo",
"2": "bar"
}
I want to get its properties by key via jq, and I have tried:
$ jq ."1" example.json
0.1
and
jq .["1"] example.json
jq: error (at example.json:4): Cannot index object with number
The result should be
"foo"
though.
The command:
jq ."1" example.json
doesn't work because the quotes are interpreted by the shell and the first argument that jq receives is .1. The command above is identical to jq .1 example.json and it is not correct as jq reports.
You need to enclose the jq program in apostrophes to prevent the shell interpret any character in it:
jq '."1"' example.json
This way, jq receives ."1" as its program and happily interprets it.
You can also put the key name into square brackets (as you have already tried) but it doesn't add any improvement, it's the same program only bloated. And it gives you more reasons to put it into apostrophes to protect it from the shell:
jq '.["1"]' example.json
Use quotes:
$ jq '."1"' example.json
"foo"
I`m trying to do a bash script for a checkpoint management server api and I am experiencing some problems.
I want to get the value in a json dictionary and for that I have to use variables. I am entering this command:
echo $rulebase | jq --arg n "$0" '.rulebase[$n].to'
and I get the next error:
jq: error: Cannot index array with string
However, If i use :
echo $rulebase | jq '.rulebase[0].to'
I get the result that I need. I dont know how to use the variables when they are a number, can anyone help me?
You need to convert the string that you give to your script to a number.
echo "$rulebase" | jq --arg n "$1" '.rulebase[$n|tonumber].to'
If you want to pass in a numeric value, use
—-argjson
instead of —-arg, which is for JSON string values.
If your jq does not support —argjson, then now would be an excellent time to upgrade if possible; otherwise, you could use tonumber.
If you have the index number in $0, just let the shell insert it by using appropriate quotes:
echo $rulebase | jq ".rulebase[$0].to"
(this being strange, having a number in $0, which normally is the program name).
You need to pass numbers as JSON args. Here
echo "$rulebase" | jq --argjson n "$my_variable" '.rulebase[$n].to'
I'm trying to use jq to get a value from the JSON that cURL returns.
This is the JSON cURL passes to jq (and, FTR, I want jq to return "VALUE-I-WANT" without the quotation marks):
[
{
"success":{
"username":"VALUE-I-WANT"
}
}
]
I initially tried this:
jq ' . | .success | .username'
and got
jq: error (at <stdin>:0): Cannot index array with string "success"
I then tried a bunch of variations, with no luck.
With a bunch of searching the web, I found this SE entry, and thought it might have been my saviour (spoiler, it wasn't). But it led me to try these:
jq -r '.[].success.username'
jq -r '.[].success'
They didn't return an error, they returned "null". Which may or may not be an improvement.
Can anybody tell me what I'm doing wrong here? And why it's wrong?
You need to pipe the output of .[] into the next filter.
jq -r '.[] | .success.username' tmp.json
tl;dr
# Extract .success.username from ALL array elements.
# .[] enumerates all array elements
# -r produces raw (unquoted) output
jq -r '.[].success.username' file.json
# Extract .success.username only from the 1st array element.
jq -r '.[0].success.username' file.json
Your input is an array, so in order to access its elements you need .[], the array/object-value iterator (as the name suggests, it can also enumerate the properties of an object):
Just . | sends the input (.) array as a whole through the pipeline, and an array only has numerical indices, so the attempt to index (access) it with .success.username fails.
Thus, simply replacing . | with .[] | in your original attempt, combined with -r to get raw (unquoted output), should solve your problem, as shown in chepner's helpful answer.
However, peak points out that since at least jq 1.3 (current as of this writing is jq 1.5) you don't strictly need a pipeline, as demonstrated in the commands at the top.
So the 2nd command in your question should work with your sample input, unless you're using an older version.
I am trying to build a json with jq with --arg arguments however I'd like for the json not to be able to have a condition if the variable is empty.
An example, if I run the following command
jq -n --arg myvar "${SOMEVAR}" '{ $myvar}'
I'd like the json in that case to be {} if myvar happens to be empty (Because the variable ${SOMEVAR} does not exist) and not { "myvar": "" } which is what I get by just running the command above.
Is there any way to achieve this through some sort of condition?
UPDATE:
Some more details about the use case
I want to build a json based on several environment variables but only include the variables that have a value.
Something like
{"varA": "value", "varB": "value"}
But only include varA if its value is defined and so on. The issue now is that if value is not defined, the property varA will still exist with an empty value and because of the multiple argument/variable nature, using an if/else to build the entire json as suggested will lead to a huge amount of conditions to cover for every possible combination of variables not existing
Suppose you have a template of variable names, in the form of an object as you have suggested you want:
{a, b, c}
Suppose also (for the sake of illustration) that you want to pull in the corresponding values from *ix environment variables. Then you just need to adjust the template, which can be done using this filter:
def adjust: with_entries( env[.key] as $v | select($v != null) | .value = $v );
Example:
Assuming the above filter, together with the following line, is in a file named adjust.jq:
{a,b,c} | adjust
then:
$ export a=123
$ jq -n -f -c adjust.jq
{"a":"123"}
You can use an if/else construct:
jq -n --arg myvar "${SOMEVAR}" 'if ($myvar|length > 0) then {$myvar} else {} end'
It's still not clear where the variable-value pairs are coming from, so maybe it would be simplest to construct the object containing the mapping before invoking jq, and then passing it in using the --argjson or --argfile option?