使用jq处理大型JSON流

aemubtdh  于 2022-12-15  发布在  其他
关注(0)|答案(3)|浏览(148)

我从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": []
}

我想用jq来提取row数据,这很简单:

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,而无需等待结束标记?

pgpifvop

pgpifvop1#

要获得:

{"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])
  ))'

产生所需的输出。

e4yzc0pl

e4yzc0pl2#

(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"]

aurhwmvo

aurhwmvo3#

多亏了“JSON Machine"库,有了一个简单且相对快速的解决方案,避免了jq的流解析器(jq --stream)的缺点(*),尽管它需要安装更多的软件。
为了简化使用,我编写了一个名为jm的脚本(可以在here中找到),使用这个脚本,只需编写:

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

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

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

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

相关问题