How do I use jq to filter an array that contains both objects and strings?

Viewed 302

So I have this input to jq:

[
  "foo",
  {
    "a": 1,
    "b": 2
  },
  {
    "a": 1,
    "b": 3
  },
  {
    "a": 2,
    "b": 2
  }
]

and I want to select all objects where b is 2 ideally as an array:

[
  {
    "a": 1,
    "b": 2
  },
  {
    "a": 2,
    "b": 2
  }
]

But the string in the list makes that difficult.

If I try:

.[]| select(.b == 2)

Then I get the error:

jq: error (at /tmp/data.json:14): Cannot index string with string "b"

Any help?

4 Answers

Other answers have suggested using ? which is very good. Another way is to use the built-in objects filter which discards an input if it is not an object:

map(objects | select(.b == 2))
#   ^^^^^^^   ^^^^^^^^^^^^^^^
#   A         B

A: filter out non-objects
B: At this point, we're dealing with objects

Slightly more verbose and perhaps less efficient?

There are a few options.


Ignore errors using ?:

map(select(.b? == 2))

docs, jqplay


Check the value's type in your select filter:

map(select(type == "object" and .b == 2))

docs, jqplay


Filter out non-objects:

map(objects | select(.b == 2))

docs, jqplay

Just as @hobbs suggested, use a ? to skip entries that doesn't have a .b.


As of the second question, I'd use map() so the result is an array as requested:

map(select(.b? == 2))
[
  {
    "a": 1,
    "b": 2
  },
  {
    "a": 2,
    "b": 2
  }
]

Try it online!

You can just add a ?: .[]| select(.b? == 2).

From the docs:

Optional Object Identifier-Index: .foo?

Just like .foo, but does not output even an error when . is not an array or an object.

Related