60

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

peak
  • 105,803
  • 17
  • 152
  • 177
idmitriev
  • 4,619
  • 4
  • 28
  • 44

6 Answers6

96

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"; .)
peak
  • 105,803
  • 17
  • 152
  • 177
  • 2
    How do you avoid getting this error? `cat fruit.json | jq '.fruit as $f | "orange" | in($f[])'` => `jq: error (at :9): Cannot check whether string has a string key`. I'm on jq 1.5.1. – Tyler Rick Nov 28 '17 at 21:38
  • 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[])'` => `true` – Tyler Rick Nov 28 '17 at 21:42
  • 2
    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... – Tyler Rick Nov 28 '17 at 21:45
  • FWIW, this solution seems to work *without* the added definition in 1.6 (or at least it seems to on [jqplay.org](https://jqplay.org/s/Gni0yF0ubw)). It's still a pretty long syntax for what seems like a common use case. – Cameron Stone Apr 16 '19 at 21:26
  • 3
    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,"](https://stedolan.github.io/jq/manual/#SQL-StyleOperators) and is distinct from `in`, which has very confusing semantics. – ravron Jul 18 '20 at 16:52
25

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

markusk
  • 6,477
  • 34
  • 39
  • 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. – peak Nov 21 '19 at 21:42
  • @peak 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. – markusk Nov 25 '19 at 12:44
  • 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. – peak Mar 28 '21 at 09:10
  • @peak 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. – markusk Apr 05 '21 at 00:58
8

[WARNING: SEE THE COMMENTS AND ALTERNATIVE ANSWERS.]

cat fruit.json | jq '.fruit | contains(["orange"])'
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
tefozi
  • 5,390
  • 5
  • 38
  • 52
5

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[]).

peak
  • 105,803
  • 17
  • 152
  • 177
aaa
  • 194
  • 1
  • 4
1

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.

Ron Sims II
  • 566
  • 5
  • 10
-2

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.