获取jq JSON解析中的第一个(或第n个)元素

101
我可以在[]中获取json的第一个元素。
$ echo '[{"a":"x", "b":true}, {"a":"XML", "b":false}]' | jq '.[1]'
{
  "a": "XML",
  "b": false
}

但如果 JSON 已经被拆解(例如,在使用 'select' 过滤条目后),我该如何选择单个条目并避免在这里看到的错误?

$ 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

23
'.[1]' 实际上获取第二个元素。 '.[0]' 将获取第一个元素。Javascript的数组是以零为基础的。 - Tony
5个回答

127

您可以将 select 的结果包装在一个数组中:

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

输出:

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

45

jq还提供first/0last/0nth/1,所以在这种情况下使用过滤器。

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

产生

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

还有其他的流式操作 'first/1', 'last/1''nth/2',使用这些数据。

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

生产

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

整洁。我如何使用jq请求检索条目数量? - Olivier Dulac
建议使用length内置函数或者reduce .[] as $x (0;.+1) - jq170727

24

之前的许多回答都是通过避免首先创建对象流来实现的。但是如果您从一系列对象流开始,例如JSON格式的应用程序日志,该怎么办?如果您的输入文件(或流)中有多个对象,则只需使用--slurp选项(或短格式的-s)即可从独立对象流中选择单个条目:

   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.
然后你可以直接索引数组。例如,要获取第二个项目(索引为1):
jq --slurp '.[1]'

将这个问题与您最初的问题结合起来,您希望从流中选择该项:

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

这导致产生以下输出:

{
  "a": "XML",
  "b": false
}

谢谢,其他答案都产生了一些变体 jq: error (at <stdin>:1): Cannot index object with number 但这个方法非常有效。 - vulpxn
太好了,这正是我所需要的;将gunzip -c的输出导入到jq中。 - twedl

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

map 接收一个 filter 以过滤数组。

这个命令

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

给出中间结果
[{"a":"x","b":true},{"a":"x","b":false}]

.[1] 取第一个元素


0

我和jbyler有相同的情况 - 我想解析N行JSON日志,其中每行都是一个单独的对象:

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

我不喜欢使用--slurp解决方案,因为这会导致jq读取整个文件,而我可能只对前N行感兴趣。

解决方案似乎是将input函数与jq -n结合使用,它可以读取单个输入对象:

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

结合range,我可以使用它来读取最多N个输入对象。
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

但是要小心将其包装成数组 - 如果做错了,会导致jq重新评估[range(10) as $i | input]表达式,所以这里我得到的不是元素[0, 1, 2],而是[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

为了安全地多次访问同一数组,您需要在它们之间使用管道:
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

通过这种方式,您可以从输入文件中获取最多N个对象,然后从此选择中提取任意对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接