How to check if element exists in array with jq
Asked Answered
D

6

69

I have an array and I need to check if elements exists in that array or to get that element from the array using jq, fruit.json:

{
    "fruit": [
        "apple", 
        "orange",
        "pomegranate",
        "apricot",
        "mango"
    ]
}


cat fruit.json | jq '.fruit .apple' 

does not work

Disengagement answered 6/4, 2017 at 15:31 Comment(0)
P
109

The semantics of 'contains' is not straightforward at all. In general, it would be better to use 'index' to test if an array has a specific value, e.g.

.fruit | index( "orange" )

However, if the item of interest is itself an array, the general form:

 ARRAY | index( [ITEM] )

should be used, e.g.:

[1, [2], 3] | index( [[2]] )  #=> 1

IN/1

If your jq has IN/1 then a better solution is to use it:

.fruit as $f | "orange" | IN($f[])

If your jq has first/1 (as does jq 1.5), then here is a fast definition of IN/1 to use:

def IN(s): first((s == .) // empty) // false;

any(_;_)

Another efficient alternative that is sometimes more convenient is to use any/2, e.g.

any(.fruit[]; . == "orange")

or equivalently:

any(.fruit[] == "orange"; .)
Publicness answered 7/4, 2017 at 3:26 Comment(9)
How do you avoid getting this error? cat fruit.json | jq '.fruit as $f | "orange" | in($f[])' => jq: error (at <stdin>:9): Cannot check whether string has a string key. I'm on jq 1.5.1.Attic
It seems that it doesn't work with the built-in in() (if it does work for you, what version are you on?). It does work with the custom IN(s) you posted: cat fruit.json | jq 'def IN(s): . as $in | first(if (s == $in) then true else empty end) ; .fruit as $f | "ap" | IN($f[])' => trueAttic
Please tell me there's a more concise way to do this (that works with string values, and doesn't count substrings as a match). The semantics of none of these straightforward-sounding functions (in, has, inside, contains—only index, like you suggested!) is straightforward.. at least when it comes to something so similar as checking if an array contains a string...Attic
FWIW, this solution seems to work without the added definition in 1.6 (or at least it seems to on jqplay.org). It's still a pretty long syntax for what seems like a common use case.Chile
I was very confused, assuming that functions were looked up case-insensitively, and this IN was the same as in, but that is not the case. IN is a "SQL-style operator," and is distinct from in, which has very confusing semantics.Alternative
jq '.fruit | map(.=="apple") | any' fruit.jsonNovia
@Novia - that’s inefficient, potentially significantly so.Publicness
@Publicness You're right. It maybe more readable.Novia
self-contained examples: jq -n '[1,2,3] | index(1) != null' and jq -n '[[1],2,3] | index([[1]]) != null' and jq -n '[1,2,3] | . as $arr | 1 | IN($arr[])' and jq -n '[1,2,3] | any(.[]; . == 1)' and jq -n '[1,2,3] | any(.[] == 1; .)'True
A
30

To have jq return success if the array fruit contains "apple", and error otherwise:

jq -e '.fruit|any(. == "apple")' fruit.json >/dev/null

To output the element(s) found, change to

jq -e '.fruit[]|select(. == "apple")' fruit.json

If searching for a fixed string, this isn't very relevant, but it might be if the select expression might match different values, e.g. if it's a regexp.

To output only distinct values, pass the results to unique.

jq '[.fruit[]|select(match("^app"))]|unique' fruit.json

will search for all fruits starting with app, and output unique values. (Note that the original expression had to be wrapped in [] in order to be passed to unique.)

Aholla answered 9/1, 2019 at 12:6 Comment(4)
Regarding the last sentence ("... the element found ..."), note that if .fruit is an array with N copies of "apple", then the filter will produce N outputs.Publicness
@Publicness I suspect printing the output is only of interest when using a select expression that will match different values, and that the fruit array only contains unique values to begin with, but fair enough, I've elaborated the answer with how to output distinct values.Aholla
Using the form a[]|select(cond)to test whether an element in the array satisfies the condition is inherently inefficient unless some mechanism is used to terminate the search once an element satisfying the condition has been found. Using any/2 is probably the simplest approach.Publicness
@Publicness Good point, I switched to any for the simplest case of just checking for existence. For the more advanced cases where the caller wants to display all matching entries, select is still needed.Aholla
C
8

[WARNING: SEE THE COMMENTS AND ALTERNATIVE ANSWERS.]

cat fruit.json | jq '.fruit | contains(["orange"])'
Cahill answered 6/4, 2017 at 15:47 Comment(1)
Also, contains only requires that "orange" be a substring of an element of the array.Cumin
C
7

For future visitors, if you happen to have the array in a variable and want to check the input against it, and you have jq 1.5 (without IN), your best option is index but with a second variable:

.inputField as $inputValue | $storedArray|index($inputValue)

This is functionally equivalent to .inputField | IN($storedArray[]).

Csch answered 28/9, 2017 at 13:53 Comment(0)
A
2

Expanding on the answers here, If you need to filter the array of fruit against another array of fruit, you could do something like this:

cat food.json | jq '[.fruit[] as $fruits | (["banana", "apple"] | contains([$fruits])) as $results | $fruits | select($results)]'

This will return an array only containing "apple" in the above sample json.

Amena answered 30/8, 2021 at 19:43 Comment(0)
E
-3

This modified sample did worked here:

jq -r '.fruit | index( "orange" )' fruit.json | tail -n 1

It gets only the last line of the output.

If it exist, it returns 0. If don't, it returns null.

Ethbinium answered 12/11, 2021 at 17:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.