将巨大的JSON文件解析为小文件

时间:2019-10-16 07:35:57

标签: python json stream jq

使用以下结构解压缩后,我大约有96 gzip的JSON,超过350 GB的JSON文件

{
  "structe": {},
  "beta": {},
  "flow": {
    "1023": {
      "0101": {
        "-LEjllNyHqdHYGntO6vu": {
          "status": "1",
          "t": 1528736191996
        },
        "-LEjllcXKaVOQu3BDpHF": {
          "status": "1",
          "t": 1528736192996
        }
      },
      "0102": {
        "-LEjllNyHqdHYGntO6vu": {
          "status": "1",
          "t": 1528736191996
        },
        "-LEjllcXKaVOQu3BDpHF": {
          "status": "1",
          "t": 1528736192996
        }
      }
    },
    "1024": {
      "0103": {
        "-LEjllNyHqdHYGntO6vu": {
          "lat": 51.128676733981,
          "lng": -113.9318991267252,
          "status": "1",
          "t": 1528736191996
        },
        "-LEjllcXKaVOQu3BDpHF": {
          "lat": 51.128676733981,
          "lng": -113.9318991267252,
          "status": "1",
          "t": 1528736192996
        }
      }
    }
  }
}

我无法将其加载到RAM中,现在我要流传输此文件并将路径flow->1023(let id1)->0101(let id2)拉到新的id1_id2.json文件中。任何人都想如何快速地做到这一点。 我正在寻找的输出就像 文件名= 1023_0101.json

{
        "-LEjllNyHqdHYGntO6vu": {
          "status": "1",
          "t": 1528736191996
        },
        "-LEjllcXKaVOQu3BDpHF": {
          "status": "1",
          "t": 1528736192996
        }
      }

3 个答案:

答案 0 :(得分:2)

这是一个使用jq的流解析器生成由$ id1,$ id2和对应的感兴趣值组成的流的解决方案;然后可以将该流传输到另一个工具(例如awk,如果方便的话)以生成所需的文件。

以下,我们使用jq食谱中的atomize

  def atomize(s):
    fromstream(foreach s as $in ( {previous:null, emit: null};
      if ($in | length == 2) and ($in|.[0][0]) != .previous and .previous != null
      then {emit: [[.previous]], previous: $in|.[0][0]}
      else { previous: ($in|.[0][0]), emit: null}
      end;
      (.emit // empty), $in) ) ;

主jq程序(用--stream -n -c调用)就是:

atomize(inputs)
| select(type == "object" and .flow)
| .flow
| keys_unsorted[] as $id1
| (.[$id1] | keys_unsorted[]) as $id2
| $id1, $id2, .[$id1][$id2]

因此,对于每个gzip文件$ gz,管道如下所示:

gunzip -c $ gz |      jq -nc --stream -f program.jq |      awk ....

有关使用awk产生所需结果的示例,请参见jq, split a huge json of array and save into file named with a value

注意事项和附录

jq的流解析器避免以牺牲速度为代价使用RAM,因此通常仅在没有其他选择时才使用--stream选项。从问题的描述来看,您似乎可以使用jq的常规解析器处理某些压缩文件,因此您可能希望快速处理这些文件,而对那些太大的文件保留“ atomize”方法。

警告

如果出现id1_id2.json冲突,问题描述并不清楚该怎么办。如果不可能发生这种碰撞,那么当然没有问题。否则,将由创建这些文件的程序来管理该突发事件。

答案 1 :(得分:1)

您可以将jq--stream选项(jq - I/O (Streaming))一起使用,该选项以流方式读取文本,从而允许程序立即开始处理大型JSON文本,而不是在解析完成后开始处理(将整个文件存储在RAM中)。

假设您输入的id字符串存储在shell变量上下文中

id1=1023; id2=0101

将庞大的gzip的输出放入以下过滤器

jq --arg v1 "$id1" --arg v2 "$id2" --stream 'fromstream(inputs)| objects | .flow[$v1][$v2]' > "$id1"_"$id2".json

(或)如果无法预先获取ID名称,而您需要在运行时获取ID名称,则直接将其名称用作

jq --stream 'fromstream(inputs)| objects | .flow."1023"."0101"'

答案 2 :(得分:0)

我首先想到的是将文件视为流,并逐行读取。已经有一些库将json文件视为流。例如,您可以从ijson库中检出片段:

对于JSON之类的

{
  "earth": {
    "europe": [
      {"name": "Paris", "type": "city", "info": { ... }},
      {"name": "Thames", "type": "river", "info": { ... }},
      // ...
    ],
    "america": [
      {"name": "Texas", "type": "state", "info": { ... }},
      // ...
    ]
  }
}

治疗如下:

import ijson

parser = ijson.parse(urlopen('http://.../'))
stream.write('<geo>')
for prefix, event, value in parser:
    if (prefix, event) == ('earth', 'map_key'):
        stream.write('<%s>' % value)
        continent = value
    elif prefix.endswith('.name'):
        stream.write('<object name="%s"/>' % value)
    elif (prefix, event) == ('earth.%s' % continent, 'end_map'):
        stream.write('</%s>' % continent)
stream.write('</geo>')