80

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

8 Answers 8

119

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

WARNING regarding `in`

In a comment, someone suggested using `in`, but `in` has very different semantics compared to `IN`. For example, consider that the following returns `true`:

jq -n '1 | in([0,4])'

Sign up to request clarification or add additional context in comments.

10 Comments

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.
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
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...
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.
@kev - that’s inefficient, potentially significantly so.
|
31

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

4 Comments

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

Comments

7

[WARNING: SEE THE COMMENTS AND ALTERNATIVE ANSWERS.]

cat fruit.json | jq '.fruit | contains(["orange"])'

2 Comments

Also, contains only requires that "orange" be a substring of an element of the array.
jq -n '"abc" | contains("b")' or jq -n '["abc"] | contains(["b"])'
4

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.

1 Comment

Using "as" was quite useful. I have never used this before. Your answer itself does not meet my requirement, but without "as", I couldn't filter my JSON correctly. Many thanks for mentioning "as"!
1

Like mentioned in other answers, contains is a massive footgun. Here's why:

$ jq 'contains(["bar"])' <<<'["foo"]'
false

$ jq 'contains(["bar"])' <<<'["foo","bar"]'
true

$ jq 'contains(["bar"])' <<<'["foo","barzzz"]'
true

Unless you explicitly want the behavior above, prefer index:

$ jq 'index("bar")' <<<'["foo"]'
null

$ jq 'index("bar")' <<<'["foo","bar"]'
1

$ jq 'index("bar")' <<<'["foo","barzzz"]'
null

In jq, null and false are considered falsy, while every other values are truthy. So you can easily use them inside conditions, like:

$ jq 'if index("foo") then true else false end' <<<'["foo"]'
true

Comments

0

had a case where i needed to check this byt for multiple elements. seems like a perfect case for the contains function:

$ jq --args 'contains ($ARGS.positional)' foobar bazbeq <<< '["foobar", "bazbeq", "xxx"]'
true
$ jq --args 'contains ($ARGS.positional)' foobar bazbeq yyy <<< '["foobar", "bazbeq", "xxx"]'
false

but there's an issue: contains checks the containment also for strings. so you have to watch out for substrings:

$ jq --args 'contains ($ARGS.positional)' foo baz <<< '["foobar", "bazbeq", "xxx"]'
true

Solutions I came up with:

wrapper + contains

Just wrap each elem in some "delimiter" - can be whatever

$ jq --args '[.[] | "#\(.)#"] | contains ([$ARGS.positional[] | "#\(.)#"])' foo bar baz beq <<< '["foobar", "bazbeq", "xxx"]'
false
$ jq --args '[.[] | "#\(.)#"] | contains ([$ARGS.positional[] | "#\(.)#"])' foobar bazbeq <<< '["foobar", "bazbeq", "xxx"]'
true

any + all

$ jq --args '[$ARGS.positional[] as $X | [$X == .[]] | any] | all' foo bar baz beq <<< '["foobar", "bazbeq", "xxx"]'
false
$ jq --args '[$ARGS.positional[] as $X | [$X == .[]] | any] | all' foobar bazbeq <<< '["foobar", "bazbeq", "xxx"]'                                              true

index + all

$ jq --args '[ . | index ($ARGS.positional[]) ] | all' foo bar baz beq <<< '["foobar", "bazbeq" , "xxx", "yyy"]'
false
$ jq --args '[ . | index ($ARGS.positional[]) ] | all' foobar bazbeq <<< '["foobar", "bazbeq" , "xxx", "yyy"]'
true

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.