Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to filter an array of objects based on values in an inner array with jq?

Given this input:

[   {     "Id": "cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b",     "Names": [       "condescending_jones",       "loving_hoover"     ]   },   {     "Id": "186db739b7509eb0114a09e14bcd16bf637019860d23c4fc20e98cbe068b55aa",     "Names": [       "foo_data"     ]   },   {     "Id": "a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19",     "Names": [       "jovial_wozniak"     ]   },   {     "Id": "76b71c496556912012c20dc3cbd37a54a1f05bffad3d5e92466900a003fbb623",     "Names": [       "bar_data"     ]   } ] 

I'm trying to construct a filter with jq that returns all objects with Ids that do not contain "data" in the inner Names array, with the output being newline-separated. For the above data, the output I'd like is:

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19 

I think I'm somewhat close with this:

(. - select(.Names[] contains("data"))) | .[] .Id 

but the select filter is not correct and it doesn't compile (get error: syntax error, unexpected IDENT).

like image 213
Abe Voelker Avatar asked Nov 02 '14 16:11

Abe Voelker


People also ask

What is jq filter?

A jq program is a "filter": it takes an input, and produces an output. There are a lot of builtin filters for extracting a particular field of an object, or converting a number to a string, or various other standard tasks.

What is jq slurp?

The slurp option ( -s ) changes the input to the jq program. It reads all the input values and build an array for the query input. Using with the raw input option ( -R ) means reading the entire input as a string. The inputs function is a special stream that emits the remaining JSON values given to the jq program.

What is jq module?

(A jq module is essentially a file that consists only of jq filter definitions ( def s) and/or module directives for including ( include ) or importing ( import ) other modules.


2 Answers

Very close! In your select expression, you have to use a pipe (|) before contains.

This filter produces the expected output.

. - map(select(.Names[] | contains ("data"))) | .[] .Id 

The jq Cookbook has an example of the syntax.

Filter objects based on the contents of a key

E.g., I only want objects whose genre key contains "house".

$ json='[{"genre":"deep house"}, {"genre": "progressive house"}, {"genre": "dubstep"}]' $ echo "$json" | jq -c '.[] | select(.genre | contains("house"))' {"genre":"deep house"} {"genre":"progressive house"} 

Colin D asks how to preserve the JSON structure of the array, so that the final output is a single JSON array rather than a stream of JSON objects.

The simplest way is to wrap the whole expression in an array constructor:

$ echo "$json" | jq -c '[ .[] | select( .genre | contains("house")) ]' [{"genre":"deep house"},{"genre":"progressive house"}] 

You can also use the map function:

$ echo "$json" | jq -c 'map(select(.genre | contains("house")))' [{"genre":"deep house"},{"genre":"progressive house"}] 

map unpacks the input array, applies the filter to every element, and creates a new array. In other words, map(f) is equivalent to [.[]|f].

like image 142
Iain Samuel McLean Elder Avatar answered Oct 05 '22 13:10

Iain Samuel McLean Elder


Here is another solution which uses any/2

map(select(any(.Names[]; contains("data"))|not)|.Id)[] 

with the sample data and the -r option it produces:

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19 
like image 31
jq170727 Avatar answered Oct 05 '22 13:10

jq170727