I have the following:
{"arr":[{"Name":"web"},{"Name":"app"}]}
I need to find out the array index of all array objects that do not have "app" in the attribute "Name".
I've tried to use a combination of "select" and "keys" but this does not work:
jq '.arr[] | select(.Name != "app") | keys'
Here's one of many possibilities:
.arr | range(0;length) as $i | select(.[$i].Name != "app") | $i
And slightly more briefly but less efficiently:
.arr | to_entries[] | select(.value.Name != "app") | .key
And if you are itching to use a for-style loop, or if you want something to think about:
foreach .arr[] as $o (-1; .+1; select($o.Name != "app"))
Here is a solution which uses tostream.
tostream
| if .[0][-1] == "Name" and .[1] == "app"
then .[0][-2]
else empty
end
Related
In jq, I can select an item in a list fairly easily:
$ echo '["a","b","c","d","e"]' | jq '.[] | select(. == ("a","c"))'
Or if you prefer to get it as an array:
$ echo '["a","b","c","d","e"]' | jq 'map(select(. == ("a","c")))'
But how do I select all of the items that are not in the list? Certainly . != ("a","c") does not work:
$ echo '["a","b","c","d","e"]' | jq 'map(select(. != ("a","c")))'
[
"a",
"b",
"b",
"c",
"d",
"d",
"e",
"e"
]
The above gives every item twice, except for "a" and "c"
Same for:
$ echo '["a","b","c","d","e"]' | jq '.[] | select(. != ("a","c"))'
"a"
"b"
"b"
"c"
"d"
"d"
"e"
"e"
How do I filter out the matching items?
The simplest and most robust (w.r.t. jq versions) approach would be to use the builtin -:
$ echo '["a","b","c","d","e"]' | jq -c '. - ["a","c"]'
["b","d","e"]
If the blacklist is very long and riddled with duplicates, then it might be appropriate to remove them (e.g. with unique).
Variations
The problem can also be solved (in jq 1.4 and up) using index and not, e.g.
["a","c"] as $blacklist
| .[] | select( . as $in | $blacklist | index($in) | not)
Or, with a variable passed in from the command-line (jq --argjson blacklist ...):
.[] | select( . as $in | $blacklist | index($in) | not)
To preserve the list structure, one can use map( select( ...) ).
With jq 1.5 or later, you could also use any or all, e.g.
def except(blacklist):
map( select( . as $in | blacklist | all(. != $in) ) );
Special case: strings
See e.g. Select entries based on multiple values in jq
I'm sure it is not the most simple solution, but it works :)
$ echo '["a","b","c","d","e"]' | jq '.[] | select(test("[^ac]"))'
Edit: one more solution - this is even worse :)
$ echo '["a","b","c","d","e"]' | jq '.[] | select(. != ("a") and . != ("b"))'
I have a json object that looks like this (prodused by i3-msg -t get_workspaces.
[
{
"name": "1",
"urgent": false
},
{
"name": "2",
"urgent": false
},
{
"name": "something",
"urgent": false
}
]
I am trying to use jq to figure out which index number in the list is based on a select query. jq have something called index(), but it seams to support only strings?
Using something like i3-msg -t get_workspaces | jq '.[] | select(.name=="something")' gives me the object I want. But I want it's index. In this case 2 (starting counting at 0)
Is this possible using jq alone?
So I provided a strategy for a solution to the OP, which OP quickly accepted. Subsequently #peak and #Jeff Mercado offered better and more complete solutions. So I have turned this into a community wiki. Please improve this answer if you can.
A straightforward solution (pointed out by #peak) is to use the builtin function, index:
map(.name == "something") | index(true)
The jq documentation confusingly suggests that index operates on strings, but it operates on arrays as well. Thus index(true) returns the index of the first true in the array of booleans produced by the map. If there is no item satisfying the condition, the result is null.
jq expresions are evaluated in a "lazy" manner, but map will traverse the entire input array. We can verify this by rewriting the above code and introducing some debug statements:
[ .[] | debug | .name == "something" ] | index(true)
As suggested by #peak, the key to doing better is to use the break statement introduced in jq 1.5:
label $out |
foreach .[] as $item (
-1;
.+1;
if $item.name == "something" then
.,
break $out
else
empty
end
) // null
Note that the // is no comment; it is the alternative operator. If the name is not found the foreach will return empty which will be converted to null by the alternative operator.
Another approach is to recursively process the array:
def get_index(name):
name as $name |
if (. == []) then
null
elif (.[0].name == $name) then
0
else
(.[1:] | get_index($name)) as $result |
if ($result == null) then null else $result+1 end
end;
get_index("something")
However this recursive implementation will use stack space proportional to the length of the array in the worst case as pointed out by #Jeff Mercado. In version 1.5 jq introduced Tail Call Optimization (TCO) which will allow us to optimize this away using a local helper function (note that this is minor adaptation to a solution provided by #Jeff Mercado so as to be consistent with the above examples):
def get_index(name):
name as $name |
def _get_index:
if (.i >= .len) then
null
elif (.array[.i].name == $name) then
.i
else
.i += 1 | _get_index
end;
{ array: ., i: 0, len: length } | _get_index;
get_index("something")
According to #peak obtaining the length of an array in jq is a constant time operation, and apparently indexing an array is inexpensive as well. I will try to find a citation for this.
Now let's try to actually measure. Here is an example of measuring the simple solution:
#!/bin/bash
jq -n '
def get_index(name):
name as $name |
map(.name == $name) | index(true)
;
def gen_input(n):
n as $n |
if ($n == 0) then
[]
else
gen_input($n-1) + [ { "name": $n, "urgent":false } ]
end
;
2000 as $n |
gen_input($n) as $i |
[(0 | while (.<$n; [ ($i | get_index(.)), .+1 ][1]))][$n-1]
'
When I run this on my machine, I get the following:
$ time ./simple
1999
real 0m10.024s
user 0m10.023s
sys 0m0.008s
If I replace this with the "fast" version of get_index:
def get_index(name):
name as $name |
label $out |
foreach .[] as $item (
-1;
.+1;
if $item.name == $name then
.,
break $out
else
empty
end
) // null;
Then I get:
$ time ./fast
1999
real 0m13.165s
user 0m13.173s
sys 0m0.000s
And if I replace it with the "fast" recursive version:
def get_index(name):
name as $name |
def _get_index:
if (.i >= .len) then
null
elif (.array[.i].name == $name) then
.i
else
.i += 1 | _get_index
end;
{ array: ., i: 0, len: length } | _get_index;
I get:
$ time ./fast-recursive
1999
real 0m52.628s
user 0m52.657s
sys 0m0.005s
Ouch! But we can do better. #peak mentioned an undocumented switch --debug-dump-disasm which lets you see how jq is compiling your code. With this you can see that modifying and passing the object to _indexof and then extracting the array, length, and index is expensive. Refactoring to just pass the index is a huge improvement, and a further refinement to avoid testing the index against the length makes it competitive with the iterative version:
def indexof($name):
(.+[{name: $name}]) as $a | # add a "sentinel"
length as $l | # note length sees original array
def _indexof:
if ($a[.].name == $name) then
if (. != $l) then . else null end
else
.+1 | _indexof
end
;
0 | _indexof
;
I get:
$ time ./fast-recursive2
null
real 0m13.238s
user 0m13.243s
sys 0m0.005s
So it appears that if each element is equally likely, and you want an average case performance, you should stick with the simple implementation. (C-coded functions tend to be fast!)
The solution originally proposed by #Jim-D using foreach would only work as intended for arrays of JSON objects, and both the originally proposed solutions are very inefficient. Their behavior in the absence of an item satisfying the condition might also have been surprising.
Solution using index/1
If you just want a quick-and-easy solution, you can use the builtin function, index, as follows:
map(.name == "something") | index(true)
If there is no item satisfying the condition, then the result will be null.
Incidentally, if you wanted ALL indices for which the condition is true, then the above is easily transformed into a super-fast solution by simply changing index to indices:
map(.name == "something") | indices(true)
Efficient solution
Here is a generic and efficient function that returns the index (i.e. offset) of the first occurrence of the item in the input array for which (item|f) is truthy (neither null nor false), and null otherwise. (In jq, javascript, and many others, the index into arrays is always 0-based.)
# 0-based index of item in input array such that f is truthy, else null
def which(f):
label $out
| foreach .[] as $x (-1; .+1; if ($x|f) then ., break $out else empty end)
// null ;
Example usage:
which(.name == "something")
Converting an array to entries will give you access to both the index and value in the array of the items. You could use that to then find the value you're looking for and get its index.
def indexof(predicate):
reduce to_entries[] as $i (null;
if (. == null) and ($i.value | predicate) then
$i.key
else
.
end
);
indexof(.name == "something")
This however does not short circuit and will go through the entire array to find the index. You'll want to return as soon as the first index has been found. Taking a more functional approach might be more appropriate.
def indexof(predicate):
def _indexof:
if .i >= .len then
null
elif (.arr[.i] | predicate) then
.i
else
.i += 1 | _indexof
end;
{ arr: ., i: 0, len: length } | _indexof;
indexof(.name == "something")
Note that the arguments are passed in to the inner function in this way to take advantage of some optimizations. Namely to take advantage of TCO, the function must not accept any additional parameters.
A still faster version can be obtained by recognizing that the array and its length do not vary:
def indexof(predicate):
. as $in
| length as $len
| def _indexof:
if . >= $len then null
elif ($in[.] | predicate) then .
else . + 1 | _indexof
end;
0 | _indexof;
Here is another version which seems to be slightly faster than the optimized versions from #peak and #jeff-mercado:
label $out | . as $elements | range(length) |
select($elements[.].name == "something") | . , break $out
IMO it is easier to read although it still relies on the break (to get the first match only).
I was doing 100 iterations on a ~1,000,000 element array (with the last element being the one to match). I only counted the user and kernel times, not the wall clock time. On average this solution took 3.4s, #peak's solution took 3.5s, and #jeff-mercado's took 3.6s. This matched what I was seeing in one off runs although to be fair I did have a run where this solution to 3.6s on average so there is unlikely to be any statistical significant difference between each solution.
I have created a csv output from an input JSON file.
Since some JSON arrays do not have their own id's, I need to add a unique id in my csv output that will be based on index of current element in its JSON array.
Is there any built-in JQ function returning the element index?
to_entries should work perfectly.
jq -n '["a","b","c"] | to_entries'
will produce
[{"key":0,"value":"a"},{"key":1,"value":"b"},{"key":2,"value":"c"}]
There are two robust (i.e., that work in jq 1.3, 1.4 and 1.5) ways to iterate through an array with an index: one is to use keys[] (which works on arrays as well as objects), and the other using range/2. These two approaches can be illustrated as follows, assuming $a is an array:
($a | keys[]) as $i | [$i, $a[$i]]
range(0; $a | length) as $i | [$i, $a[$i]]
Or more succinctly:
$a | keys[] as $i | [$i, .[$i]]
$a | range(0;length) as $i | [$i, .[$i]]
If your jq has keys_unsorted, you might wish to use it instead.
(index/1 is probably not what is needed here.)
As of jq 1.4, there's index/1 or indices/1 as outlined in the manual.
I've been banging my head against the wall for several hours on this and just can't seem to find a way to do this. I have an array of keys and an array of values, how can I generate an object? Input:
[["key1", "key2"], ["val1", "val2"]]
Output:
{"key1": "val1", "key2": "val2"}
Resolved this on github:
.[0] as $keys |
.[1] as $values |
reduce range(0; $keys|length) as $i ( {}; . + { ($keys[$i]): $values[$i] })
The current version of jq has a transpose filter that can be used to pair up the keys and values. You could use it to build out the result object rather easily.
transpose | reduce .[] as $pair ({}; .[$pair[0]] = $pair[1])
Just to be clear:
(0) Abdullah Jibaly's solution is simple, direct, efficient and generic, and should work in all versions of jq;
(1) transpose/0 is a builtin in jq 1.5 and has been available in pre-releases since Oct 2014;
(2) using transpose/0 (or zip/0 as defined above), an even shorter but still simple, fast, and generic solution to the problem is:
transpose | map( {(.[0]): .[1]} ) | add
Example:
$ jq 'transpose | map( {(.[0]): .[1]} ) | add'
Input:
[["k1","k2","k3"], [1,2,3] ]
Output:
{
"k1": 1,
"k2": 2,
"k3": 3
}
Scratch this, it doesn't actually work for any array greater than size 2.
[map(.[0]) , map(.[1])] | map({(.[0]):.[1]}) | add
Welp, I thought this would be easy, having a little prolog experience... oh man. I ended up banging my head against a wall too. Don't think I'll ever use jq ever again.
Here is a solution which uses reduce with a state object holding an iteration index and a result object. It iterates over the keys in .[0] setting corresponding values in the result from .[1]
.[1] as $v
| reduce .[0][] as $k (
{idx:0, result:{}}; .result[$k] = $v[.idx] | .idx += 1
)
| .result
I have a strange issue, this is my CSV:
Serveur;Carte;Cordon;IP;Mac;Vmnic ;Vmnic mac;Connect;Port
Dexter;eth1;405;172.16.5.117;00:24:e8:36:36:df;Vmnic0;00:50:56:56:36:df;sw-front-1;A1
Dexter;eth2;14;192.168.140.17;00:24:e8:36:36:e1;Vmnic1;00:50:56:56:36:e1; sw_eq_ds_1;3
;;;;;;;;
Gordon;eth1;404;172.16.5.124;b8:ac:6f:8d:ac:b4;Vmnic0;00:50:56:5d:ac:b4;;
Gordon;eth2;35;192.168.140.114;b8:ac:6f:8d:ac:b6;Vmnic1;00:50:56:5d:ac:b6;;
Gordon;eth3;254;192.168.33.10;b8:ac:6f:8d:ac:b8;Vmnic2;00:50:56:5d:ac:b8;;
So I imported it into an array with the following code:
$Serveur = #()
Import-Csv C:\Users\aasif\Desktop\myfile.csv -Delimiter ";" |`
ForEach-Object {
$Serveur += $_.Serveur
}
And to remove duplicate values I did this :
$Serveur = $Serveur | sort -uniq
So when I display my Array, I obtain these two values : Dexter and Gordon and a third null value
But I also get an empty value
The following code return 3
$Serveur.count
Why?
Thanks for your help
If you want exclude empty values you can do like this
$Serveur = $Serveur | ? { $_ } | sort -uniq
In case someone (like me) needs to remove empty elements from array, but without sorting:
$Serveur = $Serveur | Where-Object { $_ } | Select -Unique
You have an array with 3 elements, so the count is 3. The element you got from the line ;;;;;;;; isn't $null, but an empty string (""), so it counts as a valid element. If you want to omit empty elements from the array, filter them out as C.B. suggested.
On a more general note, I'd recommend against using the += operator. Each operation copies the entire array to a new array, which is bound to perform poorly. It's also completely unnecessary here. Simply echo the value of the field and assign the output as a whole back to a variable:
$csv = 'C:\Users\aasif\Desktop\myfile.csv'
$Serveur = Import-Csv $csv -Delim ';' | % { $_.Serveur } | ? { $_ } | sort -uniq