【问题标题】:Reading large JSON file in Deno在 Deno 中读取大型 JSON 文件
【发布时间】:2021-07-24 06:10:17
【问题描述】:

我经常发现自己在读取一个大型 JSON 文件(通常是一个对象数组),然后操作每个对象并写回一个新文件。

为了在 Node 中实现这一点(至少是读取数据部分),我通常使用 stream-json 模块做类似的事情。

const fs = require('fs');
const StreamArray = require('stream-json/streamers/StreamArray');

const pipeline = fs.createReadStream('sample.json')
  .pipe(StreamArray.withParser());

pipeline.on('data', data => {
    //do something with each object in file
});

我最近发现了 Deno,并希望能够使用 Deno 完成这个工作流程。

看起来标准库中的readJSON 方法将文件的全部内容读入内存,所以我不知道它是否适合处理大文件。

有没有一种方法可以通过使用 Deno 中内置的一些较低级别的方法从文件中流式传输数据来完成?

【问题讨论】:

  • 我认为 deno 还没有流式 API,但这是设计目标之一。

标签: deno


【解决方案1】:

现在 Deno 1.0 已经发布,以防其他人有兴趣做这样的事情。我能够拼凑出一个适用于我的用例的小类。它不像 stream-json 包那样健壮,但它可以很好地处理大型 JSON 数组。

import { EventEmitter } from "https://deno.land/std/node/events.ts";

export class JSONStream extends EventEmitter {

    private openBraceCount = 0;
    private tempUint8Array: number[] = [];
    private decoder = new TextDecoder();

    constructor (private filepath: string) {
        super();
        this.stream();
    }

    async stream() {
        console.time("Run Time");
        let file = await Deno.open(this.filepath);
        //creates iterator from reader, default buffer size is 32kb
        for await (const buffer of Deno.iter(file)) {

            for (let i = 0, len = buffer.length; i < len; i++) {
                const uint8 = buffer[ i ];

                //remove whitespace
                if (uint8 === 10 || uint8 === 13 || uint8 === 32) continue;

                //open brace
                if (uint8 === 123) {
                    if (this.openBraceCount === 0) this.tempUint8Array = [];
                    this.openBraceCount++;
                };

                this.tempUint8Array.push(uint8);

                //close brace
                if (uint8 === 125) {
                    this.openBraceCount--;
                    if (this.openBraceCount === 0) {
                        const uint8Ary = new Uint8Array(this.tempUint8Array);
                        const jsonString = this.decoder.decode(uint8Ary);
                        const object = JSON.parse(jsonString);
                        this.emit('object', object);
                    }
                };
            };
        }
        file.close();
        console.timeEnd("Run Time");
    }
}

示例用法

const stream = new JSONStream('test.json');

stream.on('object', (object: any) => {
    // do something with each object
});

处理一个约 4.8 MB 的 json 文件,其中包含约 20,000 个小对象

[
    {
      "id": 1,
      "title": "in voluptate sit officia non nesciunt quis",
      "urls": {
         "main": "https://www.placeholder.com/600/1b9d08",
         "thumbnail": "https://www.placeholder.com/150/1b9d08"
      }
    },
    {
      "id": 2,
      "title": "error quasi sunt cupiditate voluptate ea odit beatae",
      "urls": {
          "main": "https://www.placeholder.com/600/1b9d08",
          "thumbnail": "https://www.placeholder.com/150/1b9d08"
      }
    }
    ...
]

耗时 127 毫秒。

❯ deno run -A parser.ts
Run Time: 127ms

【讨论】:

  • 感谢示例代码;如果 JSON 字符串包含不平衡的 {} 对,它似乎无法处理?想知道截至 2021 年是否有更多经过实战测试的版本可用?因为这回答了 20 年 5 月 21 日
  • @TomasJ 请参阅 stackoverflow.com/a/68505468/7379821 了解完整的流式 JSON 解析器库
【解决方案2】:

我认为像 stream-json 这样的包在 Deno 上和在 NodeJs 上一样有用,因此一种方法肯定是获取该包的源代码并使其在 Deno 上运行。 (而且这个答案很快就会过时,因为有很多人在做这样的事情,而且很快就会有人——也许是你——将他们的结果公开并可以导入到任何 Deno 脚本中。)

或者,虽然这不能直接回答您的问题,但处理大型 Json 数据集的常见模式是使用包含由换行符分隔的 Json 对象的文件。 (每行一个 Json 对象。)例如,Hadoop 和 Spark、AWS S3 select,可能还有许多其他人使用这种格式。如果您可以获取该格式的输入数据,那可能会帮助您使用更多工具。然后,您还可以使用 Deno 标准库中的 readString('\n') 方法流式传输数据:https://github.com/denoland/deno_std/blob/master/io/bufio.ts

具有减少对第三方软件包依赖的额外优势。示例代码:

    import { BufReader } from "https://deno.land/std/io/bufio.ts";

    async function stream_file(filename: string) {
        const file = await Deno.open(filename);
        const bufReader = new BufReader(file);
        console.log('Reading data...');
        let line: string;
        let lineCount: number = 0;
        while ((line = await bufReader.readString('\n')) != Deno.EOF) {
            lineCount++;
            // do something with `line`.
        }
        file.close();
        console.log(`${lineCount} lines read.`)
    }

【讨论】:

  • 嗨罗伯特,我发现你的回答很有用,我想知道是否可以用 bufio 逐行编写。我查看了 bufio go 的文档,似乎有一个 writeString 方法,但它在 deno std 模块中不存在,你知道 polyfill 应该是怎样的吗?
【解决方案3】:

这是我用于包含 13,147,089 行文本的文件的代码。 请注意,它与 Roberts 的代码相同,但使用了 readLine() 而不是 readString('\n')。 readLine() 是一个低级的行阅读原语。大多数来电者应该改用readString('\n') 或使用扫描器。`

import { BufReader } from "https://deno.land/std/io/bufio.ts";

export async function stream_file(filename: string) {
  const file = await Deno.open(filename);
  const bufReader = new BufReader(file);
  console.log("Reading data...");
  let line: string | any;
  let lineCount: number = 0;
  while ((line = await bufReader.readLine()) != Deno.EOF) {
    lineCount++;
    // do something with `line`.
  }
  file.close();
  console.log(`${lineCount} lines read.`);
}

【讨论】:

    【解决方案4】:

    2021 年 7 月更新:我有同样的需求,但没有找到可行的解决方案,所以我为 Deno 编写了一个库来解决这个问题:https://github.com/xtao-org/jsonhilo

    可以像典型的基于 SAX 的解析器一样使用:

    import {JsonHigh} from 'https://deno.land/x/jsonhilo@v0.1.0/mod.js'
    const stream = JsonHigh({
      openArray: () => console.log('<array>'),
      openObject: () => console.log('<object>'),
      closeArray: () => console.log('</array>'),
      closeObject: () => console.log('</object>'),
      key: (key) => console.log(`<key>${key}</key>`),
      value: (value) => console.log(`<value type="${typeof value}">${value}</value>`),
    })
    stream.push('{"tuple": [null, true, false, 1.2e-3, "[demo]"]}')
    
    /* OUTPUT:
    <object>
    <key>tuple</key>
    <array>
    <value type="object">null</value>
    <value type="boolean">true</value>
    <value type="boolean">false</value>
    <value type="number">0.0012</value>
    <value type="string">[demo]</value>
    </array>
    </object>
    */
    

    还有一个独特的低级接口,可以非常快速(此处的基准:https://github.com/xtao-org/jsonhilo-benchmarks)无损解析。

    它在 MIT 下发布,尽情享受吧!我希望它能解决你的问题。 :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-05
      • 2014-05-19
      • 1970-01-01
      • 2021-05-29
      • 2018-08-23
      • 2018-07-21
      • 2023-04-09
      • 2017-05-29
      相关资源
      最近更新 更多