使用 jq 处理大型 JSON 流

问题描述 投票:0回答:4

我从

curl
获得一个非常大的 JSON 流(几个 GB),并尝试使用
jq
处理它。

我想用

jq
解析的相关输出打包在表示结果结构的文档中:

{
  "results":[
    {
      "columns": ["n"],

      // get this
      "data": [    
        {"row": [{"key1": "row1", "key2": "row1"}], "meta": [{"key": "value"}]},
        {"row": [{"key1": "row2", "key2": "row2"}], "meta": [{"key": "value"}]}
      //  ... millions of rows      

      ]
    }
  ],
  "errors": []
}

我想用

row
提取
jq
数据。这很简单:

curl XYZ | jq -r -c '.results[0].data[0].row[]'

结果:

{"key1": "row1", "key2": "row1"}
{"key1": "row2", "key2": "row2"}

但是,这总是要等到

curl
完成。

我使用了

--stream
选项,该选项是为了处理这个问题而设计的。我尝试了以下命令,但也等待从
curl
返回完整对象:

curl XYZ | jq -n --stream 'fromstream(1|truncate_stream(inputs)) | .[].data[].row[]'

有没有办法“跳转”到

data
字段并开始一一解析
row
,而不需要等待关闭标签?

json jq
4个回答
9
投票

获得:

{"key1": "row1", "key2": "row1"}
{"key1": "row2", "key2": "row2"}

来自:

{
  "results":[
    {
      "columns": ["n"],
      "data": [    
        {"row": [{"key1": "row1", "key2": "row1"}], "meta": [{"key": "value"}]},
        {"row": [{"key1": "row2", "key2": "row2"}], "meta": [{"key": "value"}]}
      ]
    }
  ],
  "errors": []
}

执行以下操作,相当于

jq -c '.results[].data[].row[]'
,但使用流式传输:

jq -cn --stream 'fromstream(1|truncate_stream(inputs | select(.[0][0] == "results" and .[0][2] == "data" and .[0][4] == "row") | del(.[0][0:5])))'

它的作用是:

  • 将 JSON 转换为流(使用
    --stream
  • 选择路径
    .results[].data[].row[]
    (使用
    select(.[0][0] == "results" and .[0][2] == "data" and .[0][4] == "row"
  • 丢弃路径的初始部分,例如
    "results",0,"data",0,"row"
    (带有
    del(.[0][0:5])
  • 最后使用
    jq FAQ
    中的 fromstream(1|truncate_stream(…))
  • 模式将生成的 jq 流转回预期的 JSON

例如:

echo '
  {
    "results":[
      {
        "columns": ["n"],
        "data": [    
          {"row": [{"key1": "row1", "key2": "row1"}], "meta": [{"key": "value"}]},
          {"row": [{"key1": "row2", "key2": "row2"}], "meta": [{"key": "value"}]}
        ]
      }
    ],
    "errors": []
  }
' | jq -cn --stream '
  fromstream(1|truncate_stream(
    inputs | select(
      .[0][0] == "results" and 
      .[0][2] == "data" and 
      .[0][4] == "row"
    ) | del(.[0][0:5])
  ))'

产生所需的输出。


5
投票

(1) 您将使用的香草过滤器如下:

jq -r -c '.results[0].data[].row'

(2) 此处使用流式解析器的一种方法是使用它来处理

.results[0].data
的输出,但这两个步骤的组合可能会比普通方法慢。

(3) 要产生您想要的输出,您可以运行:

jq -nc --stream '
  fromstream(inputs
    | select( [.[0][0,2,4]] == ["results", "data", "row"])
    | del(.[0][0:5]) )'

(4) 或者,您可能希望尝试以下方法:

jq -nc --stream 'inputs
      | select(length==2)
      | select( [.[0][0,2,4]] == ["results", "data", "row"])
      | [ .[0][6], .[1]] '

对于说明性输入,最后一次调用的输出将是:


["key1","row1"]
["key2","row1"]
["key1","row2"]
["key2","row2"]


0
投票

感谢“JSON Machine”库,对原始问题有了一个简单且相对快速的解决方案,避免了 jq 流解析器(jq --stream)的缺点(*),尽管它确实需要安装更多软件。

为了使其使用起来简单,我编写了一个名为

jm
的脚本(可以在 here 找到)。有了这个脚本,只需写:

curl ... | jm --pointer /results/0/data

或者,如果您想从 .results 数组中的所有对象流式传输 .data:

curl ... | jm --pointer /results/-/data

(*) 主要缺点是缓慢和模糊。


0
投票
当处理巨大的数组作为输入(通过文件或标准输入)或性能至关重要时,

jq 流至关重要。

使用 jq 流时的一个简化是重新创建每个单独的对象并应用普通实现(当性能不太相关时 - 例如:小型流对象)。

@peak指出的普通解决方案:

echo '{...}' | jq -r -c '.results[0].data[].row'

注意:这也适用于独立的流对象(不在数组中)

使用 jq 流并重新创建对象(独立流对象)

echo '{...}' | jq --stream -nc 'fromstream(0|truncate_stream(inputs))|.results[0].data[].row'

使用jq流并重新创建对象(在一个巨大的数组中流式传输对象)

echo '[{...}]' | jq --stream -nc 'fromstream(1|truncate_stream(inputs))|.results[0].data[].row'
© www.soinside.com 2019 - 2024. All rights reserved.