解析大型JSON文件时两个NodeJS流之间的竞争条件

时间:2019-02-22 17:26:20

标签: node.js json

我必须解析具有以下格式的大型(100 MB以上)JSON文件:

{
 "metadata": {
   "account_id": 1234
   // etc.
 },
 "transactions": [
   {
     "transaction_id": 1234,
     "amount": 2
   },
   // etc. for (potentially) 1000's of lines
 ]
}

此解析的输出是一个JSON数组,其中account_id附加到每个transactions

[
 {
   "account_id": 1234,
   "transaction_id": 1234,
   "amount": 2
 },
 // etc.
]

我正在使用stream-json库来避免将整个文件同时加载到内存中。 stream-json允许我pick个单个属性,然后一次流化一个属性,具体取决于它们是array还是object

我还试图通过将JSON文件的读取传递到两个单独的流which is possible in nodejs中来避免两次解析JSON。

我正在使用Transform流来生成输出,并在存储account_id的Transform流对象上设置了一个属性。

下面的伪代码(具有明显的竞争条件):

const { parser } = require('stream-json');
const { pick } = require('stream-json/filters/Pick');
const { streamArray } = require('stream-json/streamers/StreamArray');
const { streamObject } = require('stream-json/streamers/StreamObject');
const Chain = require('stream-chain');
const { Transform } = require('stream');

let createOutputObject = new Transform({
 writableObjectMode:true,
 readableObjectMode:true,
 transform(chunk, enc, next) => {
  if (createOuptutObject.account_id !== null) {
     // generate the output object
  } else {
     // Somehow store the chunk until we get the account_id...
  } 
 } 
});
createOutputObject.account_id = null;

let jsonRead = fs.createReadStream('myJSON.json');
let metadataPipline = new Chain([
  jsonRead,
  parser(),
  pick({filter: 'metadata'}),
  streamObject(),
]);

metadataPipeline.on('data', data => {
 if (data.key === 'account_id') {
  createOutputObject.account_id = data.value;
 }
});

let generatorPipeline = new Chain([
 jsonRead, // Note same Readable stream as above
 parser(),
 pick({filter: 'tracks'}),
 streamArray(),
 createOutputObject,
 transformToJSONArray(),
 fs.createWriteStream('myOutput.json')
]);

要解决这种竞争情况(即在设置account_id之前转换为JSON数组),我尝试过:

  • 使用createOutputObject.cork()保留数据,直到设置account_id
    • 数据刚刚传递到transformToJSONArray()
  • chunk保留在createOutputObject中的数组中,直到设置了account_id
    • 无法确定在设置chunk之后如何重新添加存储的account_id
  • 稍后使用setImmediate()process.nextTick()呼叫createOutputObject.transform,希望设置account_id
    • 堆栈重载,所以什么也做不了。

我已经考虑过使用流json的streamValues函数,这将允许我执行pickmetadata的{​​{1}}。但是documentation使我相信transactions all 都将被加载到内存中,这是我要避免的事情:

  

作为每个流媒体,它假定单个对象可以容纳在内存中,但是应该流传输整个文件或任何其他源。

还有其他可以解决这种竞争状况的东西吗?无论如何,我可以避免两次解析此大型JSON流吗?

0 个答案:

没有答案