如何在 JavaScript 中解析大型 JSON 流中的项目?

Lan*_*ard 5 javascript tree parsing json stream

所以我下载了 Wikidata JSON 转储,它大约 90GB,太大而无法加载到内存中。它由一个简单的 JSON 结构组成,如下所示:

[
  item,
  item,
  item,
  ...
]
Run Code Online (Sandbox Code Playgroud)

每个“项目”看起来像这样:

{
  "type": "item",
  "id": "Q23",
  "labels": {
    "<lang>": obj
  },
  "descriptions": {
    "<lang>": {
      "language": "<lang>",
      "value": "<string>"
    },
  },
  "aliases": {
    "<key>": [
      obj,
      obj,
    ],
  },
  "claims": {
    "<keyID>": [
       {
        "mainsnak": {
          "snaktype": "value",
          "property": "<keyID>",
          "datavalue": {
            "value": {
              "entity-type": "<type>",
              "numeric-id": <num>,
              "id": "<id>"
            },
            "type": "wikibase-entityid"
          },
          "datatype": "wikibase-item"
        },
        "type": "statement",
        "id": "<anotherId>",
        "rank": "preferred",
        "references": [
          {
            "hash": "<hash>",
            "snaks": {
              "<keyIDX>": [
                {
                  "snaktype": "value",
                  "property": "P854",
                  "datavalue": obj,
                  "datatype": "url"
                }
              ]
            },
            "snaks-order": [
              "<propID>"
            ]
          }
        ]
      }
    ]
  },
  "sitelinks": {
    "<lang>wiki": {
      "site": "<lang>wiki",
      "title": "<string>",
      "badges": []
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

JSON 流配置如下:

const fs   = require('fs')
const zlib = require('zlib')
const { parser } = require('stream-json')

let stream = fs.createReadStream('./wikidata/latest-all.json.gz')
stream
  .pipe(zlib.createGunzip())
  .pipe(parser())
  .on('data', buildItem)

function buildItem(data) {
  switch (data.name) {
    case `startArray`:
      break
    case `startObject`:
      break
    case `startKey`:
      break
    case `stringChunk`:
      break
    case `endKey`:
      break
    case `keyValue`:
      break
    case `startString`:
      break
    case `endString`:
      break
    case `stringValue`:
      break
    case `endObject`:
      break
    case `endArray`:
      break
  }
}
Run Code Online (Sandbox Code Playgroud)

注意buildItem有关键信息,它表明 JSON 流发出这样的对象(这些是日志):

{ name: 'startArray' }
{ name: 'startObject' }
{ name: 'startKey' }
{ name: 'startString' }
{ name: 'stringValue', value: 'type' }
{ name: 'endString' }
...
Run Code Online (Sandbox Code Playgroud)

您如何将其解析为item上述对象?将这个线性流解析为一棵树是非常难以理解的。

JSON 流的输出示例在这里,如果有帮助,您可以使用它来测试解析器。

Ste*_*eve 5

使用内置函数 (StreamArray)

stream-json已经具有将流转换为对象的内置函数(在这种情况下,您正在寻找StreamArray)。您可能想要使用内置函数,因为它们在编码时考虑到了性能。

要使用它,它看起来像:

const fs   = require('fs')
const zlib = require('zlib')
const { parser } = require('stream-json')
const { streamArray } = require('stream-json/streamers/StreamArray')

let stream = fs.createReadStream('./wikidata/latest-all.json.gz')
stream
  .pipe(zlib.createGunzip())
  .pipe(parser())
  .pipe(streamArray())
  .on('data', d => processData(d.value))

function processData(data) {
  console.log(data)
}

Run Code Online (Sandbox Code Playgroud)

我建议查看https://github.com/uhop/stream-json/wiki上的 wiki 以获取更多信息,因为它具有附加功能,特别是用于过滤或转换,这可能对您有用,特别是如果速度是一个问题。