【问题标题】:Using jq, how can I split a JSON stream of objects into separate files based on the values of an object property?使用 jq,如何根据对象属性的值将 JSON 对象流拆分为单独的文件?
【发布时间】:2019-07-10 12:38:36
【问题描述】:

我有一个名为 input.json 的非常大的文件(20GB+ 压缩),其中包含如下 JSON 对象流:

{
    "timestamp": "12345",
    "name": "Some name",
    "type": "typea"
}
{
    "timestamp": "12345",
    "name": "Some name",
    "type": "typea"
}
{
    "timestamp": "12345",
    "name": "Some name",
    "type": "typeb"
}

我想根据它们的type 属性将该文件拆分为多个文件:typea.jsontypeb.json 等,每个文件都包含它们自己的只有匹配类型属性的 json 对象流。

对于较小的文件,我已经设法解决了这个问题,但是对于这么大的文件,我的 AWS 实例内存不足。由于我希望降低内存使用量,我知道我需要使用 --stream,但我正在努力了解如何实现这一目标。

cat input.json | jq -c --stream 'select(.[0][0]=="type") | .[1]' 将返回每个类型属性的值,但我如何使用它来过滤对象?

任何帮助将不胜感激!

【问题讨论】:

  • 有多少不同的类型? (如果我们在一次传递中扇出多个进程,我们需要多少个文件描述符保持打开状态?如果我们对每种类型进行一次传递,我们需要多少次传递?)
  • 你真的不需要在这里需要--stream,顺便说一句。如果您的对象较大,则需要这样做,但它们都足够小,可以单独处理。
  • 顺便说一句,这个输入根本不是(错误的,最初不是)有效的 JSON。 jq .,输入您的示例输入,失败并显示 parse error: Invalid literal at line 2, column 14
  • (我可以看到需要--stream 的唯一情况是问题是否歪曲了您的数据格式,并且对象位于其他更大的对象中,而不是出现在顶级对象中)。跨度>
  • @CharlesDuffy 大约有 5-10 种类型。感谢您对 --stream 的帮助,看来我误解了它的用途。

标签: json bash stream jq partitioning


【解决方案1】:

假设文件中的 JSON 对象相对较小(不超过几 MB),则不需要使用(相当复杂的)“--stream”命令行选项,主要是在输入是(或包括)单个巨大的 JSON 实体。

然而,仍有几个选择要做。主要的在Split a JSON file into separate files 进行了描述,这是一种多遍方法(N 或 (N+1) 次调用 jq,其中 N 是输出文件的数量),以及只涉及一次调用 jq 的方法,然后调用诸如awk 之类的程序来执行实际的文件分区。每种方法都有其优点和缺点,但是如果可以接受 N 次读取输入文件,那么第一种方法可能会更好。

要估计所需的总计算资源,最好测量运行jq empty input.json所使用的资源

(根据您的简短记录,听起来您遇到的内存问题主要是由于解压缩文件造成的。)

【讨论】:

  • 啊。感谢您的帮助和有用资源的链接。文件的解压缩确实可能导致内存问题,我将对其进行研究并报告。
  • 是的,内存问题是由解压过程引起的。我已经设法让它工作,现在将尝试你的方法和查尔斯达菲的方法来找出在我的用例中哪个更快。再次感谢您的帮助
【解决方案2】:

使用jq 拆分成以 NUL 分隔的(类型、文档)对流,并使用本机 bash(4.1 或更高版本)使用一组持久的文件描述符写入这些文档:

#!/usr/bin/env bash
case $BASH_VERSION in ''|[1-3].*|4.0*) echo "ERROR: Bash 4.1 needed" >&2; exit 1;; esac

declare -A output_fds=( )

while IFS= read -r -d '' type && IFS= read -r -d '' content; do
  if [[ ${output_fds[$type]} ]]; then  # already have a file handle for this output file?
    curr_fd=${output_fds[$type]}       # reuse it, then.
  else
    exec {curr_fd}>"$type.json"        # open a new output file...
    output_fds[$type]=$curr_fd         # and store its file descriptor for use.
  fi
  printf '%s\n' "$content" >&"$curr_fd"
done < <(jq -j '(.type) + "\u0000" + (. | tojson) + "\u0000"')

这永远不会一次将多于几条记录(诚然,每条记录可能有多个副本)读入内存,因此只要记录大小合理,它就可以处理任意大的文件。

【讨论】:

  • 我很乐意支持这个答案,只是我的时间显示它比 jq+awk 解决方案慢了大约 10 倍......
  • awk 后面的一个以 10 为基数的数量级正是我在 bash 中进行文本处理时的目标,因为 bash 逐个字符读取,而 awk 执行缓冲读取。也就是说——你认为这种性能差异是不可接受的;我认为这意味着我正在为正在使用的运行时编写近乎最佳的代码。 :)
  • 我没有说不可接受。顺便说一句,对于较大的 input.json,该比率会变得更糟。对于大约 80,000 个对象,我的测量结果是 17:1。
  • 这令人惊讶——我预计它至少比 awk 慢一个数量级;我期望它的大小会变慢。你对这种关系有多确定?
  • 确实,到目前为止,我只仔细查看了两个输入文件,但是这两个文件中较大的只有 ~60MB,与 OP 的 input.json 相比,这显得微不足道。 (顺便说一句,17:1 的比例适用于 800K 对象,而不是我打错的 80K。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-19
  • 2023-04-11
相关资源
最近更新 更多