97

I can get the 1st element in a json inside []

$ echo '[{"a":"x", "b":true}, {"a":"XML", "b":false}]' | jq '.[1]'
{
  "a": "XML",
  "b": false
}

But if the json is already disassembled (for instance, after filtering entries using 'select'), how can I choose a single entry and avoid the error seen here?

$ echo '[{"a":"x", "b":true}, {"a":"x", "b":false},{"a":"XML", "b":false}]' | jq '.[] | select( .a == "x")'
{
  "a": "x",
  "b": true
}
{
  "a": "x",
  "b": false
}
$ echo '[{"a":"x", "b":true}, {"a":"x", "b":false},{"a":"XML", "b":false}]' | jq '.[] | select( .a == "x") | .[1]'
jq: error (at <stdin>:1): Cannot index object with number
Demian Glait
  • 973
  • 1
  • 6
  • 4
  • 22
    `'.[1]'` actually gets the second element. `'.[0]'` will get you the first. Javascript's arrays are zero-based. – Tony Nov 14 '18 at 20:54

5 Answers5

123

You can wrap the results from select in an array:

jq '[.[]|select(.a=="x")][0]' your.json

Output:

{
  "a": "x",
  "b": false
}
aymericbeaumet
  • 6,853
  • 2
  • 37
  • 50
hek2mgl
  • 152,036
  • 28
  • 249
  • 266
40

jq also provides first/0, last/0, nth/1 so in this case the filter

  ( map(select(.a == "x")) | first  )
, ( map(select(.a == "x")) | last   ) 
, ( map(select(.a == "x")) | nth(1) )

produces

{
  "a": "x",
  "b": true
}
{
  "a": "x",
  "b": false
}
{
  "a": "x",
  "b": false
}

Additional streaming forms 'first/1', 'last/1' and 'nth/2' are also available so with this data

  ( first(.[]  | select(.a == "x")) )   
, ( last(.[]   | select(.a == "x")) )
, ( nth(1; .[] | select(.a == "x")) )

produces

{
  "a": "x",
  "b": true
}
{
  "a": "x",
  "b": false
}
{
  "a": "x",
  "b": false
}
doelleri
  • 19,232
  • 5
  • 61
  • 65
jq170727
  • 13,159
  • 3
  • 46
  • 56
21

Many of the previous answers work by avoiding to create a stream of objects in the first place. But what if you are starting with a stream of objects—for example JSON-formatted application logs? If your input file (or stream) has multiple objects in it, all you need to do to choose a single entry from a stream of independent objects is to use the --slurp option (or -s for the short form):

   o   --slurp/-s:

       Instead of running the filter for each JSON object in the input, read 
       the entire input stream into a large array and run the filter just once.

Then you can just index into the array. For example, to get second item (with index 1):

jq --slurp '.[1]'

Putting this together with your original question, where you want to select that item from a stream:

echo '{"a":"x", "b":true} {"a":"XML", "b":false}' | jq --slurp '.[1]'

which results in this output:

{
  "a": "XML",
  "b": false
}
jbyler
  • 7,200
  • 3
  • 34
  • 42
  • *Thank you*, all other answers yielded some variation of `jq: error (at :1): Cannot index object with number` but this worked like a charm. – vulpxn Oct 05 '21 at 20:06
  • This is great, this is exactly what I needed; piping output from `gunzip -c` to `jq` – twedl Jan 12 '22 at 16:37
7

use map

cat raw.json|jq -r -c 'map(select(.a=="x"))|.[1]'

map receives a filter to filter an array.

this command

cat raw.json|jq -r -c 'map(select(.a=="x"))'

give the middle result

[{"a":"x","b":true},{"a":"x","b":false}]

.[1] take the first element

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
tink
  • 123
  • 6
0

I have the same scenario as jbyler - I want to parse N lines of a JSON log, where each line is a single object:

host ~ # head /var/log/nginx/access.log.json | cut -c 1-80 
{"remote_addr":"127.0.0.1","remote_port":"47700","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"35576","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"47708","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"52974","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"52976","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"51414","time_iso8601":"2022-08-31T00:0
{"remote_addr":"127.0.0.1","remote_port":"41942","time_iso8601":"2022-08-31T00:1
{"remote_addr":"127.0.0.1","remote_port":"41946","time_iso8601":"2022-08-31T00:1
{"remote_addr":"127.0.0.1","remote_port":"37982","time_iso8601":"2022-08-31T00:1
{"remote_addr":"127.0.0.1","remote_port":"56602","time_iso8601":"2022-08-31T00:1

I do not like the --slurp solution because this will cause jq to read the entire file, when I might only be interested in the first N lines.

The solution for this seems to be the input function combined with jq -n, which reads a single input object:

host ~ # cat /var/log/nginx/access.log.json | jq -r -n 'input | [.remote_addr, .remote_port, .time_iso8601] | @tsv' 
127.0.0.1       47700   2022-08-31T00:02:53+02:00

Combined with range, I can use it to read up to N input objects:

host ~ # cat /var/log/nginx/access.log.json | jq -r -n 'range(10) as $i | input | [.remote_addr, .remote_port, .time_iso8601] | @tsv'
127.0.0.1       47700   2022-08-31T00:02:53+02:00
127.0.0.1       35576   2022-08-31T00:02:53+02:00
127.0.0.1       47708   2022-08-31T00:02:53+02:00
127.0.0.1       52974   2022-08-31T00:07:53+02:00
127.0.0.1       52976   2022-08-31T00:07:53+02:00
127.0.0.1       51414   2022-08-31T00:07:58+02:00
127.0.0.1       41942   2022-08-31T00:12:53+02:00
127.0.0.1       41946   2022-08-31T00:12:53+02:00
127.0.0.1       37982   2022-08-31T00:13:03+02:00
127.0.0.1       56602   2022-08-31T00:17:53+02:00

Be careful with wrapping this into an array though - doing it wrong will cause jq to reevaluate the [range(10) as $i | input] expression, so here I'm not getting elements [0, 1, 2], but [0, 11, 22]:

# WRONG, DON'T DO THIS
host ~ # cat /var/log/nginx/access.log.json | jq -r -n '[range(10) as $i | input][0,1,2] | [.remote_addr, .remote_port, .time_iso8601] | @tsv'
127.0.0.1       47700   2022-08-31T00:02:53+02:00
127.0.0.1       59784   2022-08-31T00:18:22+02:00
127.0.0.1       34316   2022-08-31T00:37:53+02:00

To safely access the same array multiple times, you need to have a pipe in between:

host ~ # cat /var/log/nginx/access.log.json | jq -r -n '[range(10) as $i | input] | .[0,1,2] | [.remote_addr, .remote_port, .time_iso8601] | @tsv'
127.0.0.1       47700   2022-08-31T00:02:53+02:00
127.0.0.1       35576   2022-08-31T00:02:53+02:00
127.0.0.1       47708   2022-08-31T00:02:53+02:00

This way, you can take up to N objects from the input file, and then extract arbitrary ones from this selection.

Martin von Wittich
  • 350
  • 1
  • 5
  • 19