programing

Nodejs에서 큰 JSON 파일 구문 분석

linuxpc 2023. 3. 22. 20:48
반응형

Nodejs에서 큰 JSON 파일 구문 분석

많은 JavaScript 객체를 JSON 형식으로 저장하는 파일이 있는데 파일을 읽고 각 개체를 만들고 그 작업을 수행해야 합니다(내 경우 DB에 삽입).JavaScript 객체는 다음과 같은 형식으로 나타낼 수 있습니다.

포맷 A:

[{name: 'thing1'},
....
{name: 'thing999999999'}]

또는 형식 B:

{name: 'thing1'}         // <== My choice.
...
{name: 'thing999999999'}

에 주의:...JSON을 사용하다, 「」를 사용할 수 .JSON.parse()음음음같 뭇매하다

fs.readFile(filePath, 'utf-8', function (err, fileContents) {
  if (err) throw err;
  console.log(JSON.parse(fileContents));
});

다만, 파일이 매우 클 수 있기 때문에, 그것을 실현하기 위해서 스트림을 사용하고 싶다고 생각하고 있습니다.에서 볼 수 있는 이 언제든지 청크로 될 수 파일 을 사용하려면 어떻게 ?그래서 어떻게 하면JSON.parse()런런물 건건?

각 오브젝트는 개별 데이터 청크로 읽는 것이 이상적이지만, 어떻게 읽어야 할지 잘 모르겠습니다.

var importStream = fs.createReadStream(filePath, {flags: 'r', encoding: 'utf-8'});
importStream.on('data', function(chunk) {

    var pleaseBeAJSObject = JSON.parse(chunk);           
    // insert pleaseBeAJSObject in a database
});
importStream.on('end', function(item) {
   console.log("Woot, imported objects into the database!");
});*/

메모리는 파일 전체를 읽어 들이지 않도록 하고 싶습니다.시간 효율은 나에게 중요하지 않다.네, 한 번에 여러 개체를 읽고 모든 개체를 삽입할 수 있지만, 이는 성능 조정입니다. 파일 내에 포함된 개체의 수에 관계없이 메모리 과부하가 발생하지 않도록 보장하는 방법이 필요합니다.

사용할 수 있습니다.FormatA ★★★★★★★★★★★★★★★★★」FormatB또는 다른 것을 입력해 주세요.★★★★★★★★★★★★★★★★★★!

파일을 한 줄씩 처리하려면 파일 읽기와 해당 입력에 적용되는 코드를 분리하기만 하면 됩니다.이것은, 새로운 회선에 도달할 때까지 입력을 버퍼링 하는 것으로 실현할 수 있습니다.각 행에 1개의 JSON 오브젝트가 있다고 가정합니다(기본적으로는 포맷B).

var stream = fs.createReadStream(filePath, {flags: 'r', encoding: 'utf-8'});
var buf = '';

stream.on('data', function(d) {
    buf += d.toString(); // when data is read, stash it in a string buffer
    pump(); // then process the buffer
});

function pump() {
    var pos;

    while ((pos = buf.indexOf('\n')) >= 0) { // keep going while there's a newline somewhere in the buffer
        if (pos == 0) { // if there's more than one newline in a row, the buffer will now start with a newline
            buf = buf.slice(1); // discard it
            continue; // so that the next iteration will start with data
        }
        processLine(buf.slice(0,pos)); // hand off the line
        buf = buf.slice(pos+1); // and slice the processed data off the buffer
    }
}

function processLine(line) { // here's where we do something with a line

    if (line[line.length-1] == '\r') line=line.substr(0,line.length-1); // discard CR (0x0D)

    if (line.length > 0) { // ignore empty lines
        var obj = JSON.parse(line); // parse the JSON
        console.log(obj); // do something with the data here!
    }
}

시스템으로부터 되며, 그 후 파일 스트림은 버퍼에 됩니다.pump출됩니니다다

「」는 「」입니다.pump을 사용법다음 번에 스트림이 데이터를 가져올 때 더 많은 데이터(및 잠재적으로 새 행)가 버퍼에 추가되고 완전한 개체가 생성됩니다.

줄바꿈을 하다pump줄를 process 한 번while이 방법으로 현재 청크로 읽힌 모든 행을 처리할 수 있습니다.

ㅇㅇㅇㅇㅇ.process아, 아, 아, 아, 아, 아, 1번.는, 문자하기 위해서)를하고 나서, 「」( 「」와「CRLF를 합니다.JSON.parse필요한 할 수 .이 시점에서 오브젝트로 필요한 것은 무엇이든 할 수 있습니다.

:JSON.parse는 입력으로 받아들이는 내용에 대해 엄격합니다.ID 및 문자열 은 큰따옴표로 묶어야 합니다.바꿔 말하면{name:'thing1'}발생하므로 에러를 사용해야 .사용할 필요가 있습니다.{"name":"thing1"}.

메모리에는 한 번에 데이터 청크를 초과할 수 없기 때문에 메모리 효율이 매우 높습니다.그것은 또한 매우 빠를 것이다.빠른 테스트 결과 10,000개의 행을 15ms 미만으로 처리했습니다.

스트리밍 JSON 파서를 쓰는 것도 재밌을 것 같아서 빨리 검색해봐야겠다고 생각했습니다.

알고보니 있더군요

방금 발견했기 때문에 사용하지 않은 것이 분명하기 때문에 품질에 대해서는 코멘트는 할 수 없지만, 효과가 있는지 알고 싶습니다.

다음 Javascript와 를 고려하여 동작합니다.

stream.pipe(JSONStream.parse('*'))
  .on('data', (d) => {
    console.log(typeof d);
    console.log("isString: " + _.isString(d))
  });

스트림이 객체의 배열인 경우 들어오는 객체가 기록됩니다.따라서 버퍼링되는 것은 한 번에 하나의 객체뿐입니다.

2014년 10월 현재 다음과 같은 작업을 수행할 수 있습니다(JSONStream 사용). https://www.npmjs.org/package/JSONStream

var fs = require('fs'),
    JSONStream = require('JSONStream'),

var getStream() = function () {
    var jsonData = 'myData.json',
        stream = fs.createReadStream(jsonData, { encoding: 'utf8' }),
        parser = JSONStream.parse('*');
    return stream.pipe(parser);
}

getStream().pipe(MyTransformToDoWhateverProcessingAsNeeded).on('error', function (err) {
    // handle any errors
});

작업 예제를 사용하여 시연하려면:

npm install JSONStream event-stream

data.json:

{
  "greeting": "hello world"
}

hello.dello:

var fs = require('fs'),
    JSONStream = require('JSONStream'),
    es = require('event-stream');

var getStream = function () {
    var jsonData = 'data.json',
        stream = fs.createReadStream(jsonData, { encoding: 'utf8' }),
        parser = JSONStream.parse('*');
    return stream.pipe(parser);
};

getStream()
    .pipe(es.mapSync(function (data) {
        console.log(data);
    }));
$ node hello.js
// hello world

비슷한 요구 사항이 있었는데 노드 js에서 큰 json 파일을 읽고 청크로 데이터를 처리하고 api를 호출하여 mongodb에 저장해야 합니다.inputFile.json은 다음과 같습니다.

{
 "customers":[
       { /*customer data*/},
       { /*customer data*/},
       { /*customer data*/}....
      ]
}

이를 위해 JsonStream과 EventStream을 동시에 사용했습니다.

var JSONStream = require("JSONStream");
var es = require("event-stream");

fileStream = fs.createReadStream(filePath, { encoding: "utf8" });
fileStream.pipe(JSONStream.parse("customers.*")).pipe(
  es.through(function(data) {
    console.log("printing one customer object read from file ::");
    console.log(data);
    this.pause();
    processOneCustomer(data, this);
    return data;
  }),
  function end() {
    console.log("stream reading ended");
    this.emit("end");
  }
);

function processOneCustomer(data, es) {
  DataModel.save(function(err, dataModel) {
    es.resume();
  });
}

가능하면 JSON 파일 전체를 메모리로 읽어 들이는 것을 피하고 싶은 것은 알고 있습니다만, 메모리가 있으면 퍼포먼스 면에서는 나쁘지 않을지도 모릅니다.json 파일에서 node.js의 require()를 사용하면 데이터가 매우 빠르게 메모리에 로드됩니다.

81MB 지오제이슨 파일에서 각 기능의 속성을 인쇄했을 때의 성능을 알아보기 위해 두 가지 테스트를 수행했습니다.

파일 를 Geojson으로 .var data = require('./geo.json')3330밀리초가 걸리고 각 기능에서 속성을 출력하는 데 804밀리초가 걸려서 총 4134밀리초가 걸렸습니다.그러나 node.js는 411MB의 메모리를 사용하고 있는 것으로 나타났습니다.

두 번째 테스트에서는 JSONStream + event-stream에서 @arcseldon의 답을 사용했습니다.필요한 것만 선택하도록 JSONPath 쿼리를 수정했습니다.이번에는 메모리가 82MB를 넘지 않았지만, 이제 이 모든 것을 완료하는 데 70초가 걸렸습니다!

BFJ라는 모듈을 만들었습니다.구체적으로는, 그 방법은bfj.match을 JSON의할 수 .

const bfj = require('bfj');
const fs = require('fs');

const stream = fs.createReadStream(filePath);

bfj.match(stream, (key, value, depth) => depth === 0, { ndjson: true })
  .on('data', object => {
    // do whatever you need to do with object
  })
  .on('dataError', error => {
    // a syntax error was found in the JSON
  })
  .on('error', error => {
    // some kind of operational error occurred
  })
  .on('end', error => {
    // finished processing the stream
  });

서서,,bfj.match항목을 하고 3개의합니다.인수는 다음과 같습니다.

  1. 입력 JSON을 포함하는 판독 가능한 스트림입니다.

  2. 해석된 JSON에서 결과 스트림으로 푸시되는 항목을 나타내는 술어입니다.

  3. 입력이 줄 바꿈으로 구분된 JSON임을 나타내는 옵션개체(질문에서 형식 B를 처리하는 것으로 형식 A에는 필요하지 않습니다).

마자 마자 마자 마자 마자 마자 마자 마자 마자 마자 마자.bfj.match는 입력 스트림의 깊이에서 JSON을 해석하여 각 값을 가진 술어를 호출하여 해당 항목을 결과 스트림에 푸시할지 여부를 결정합니다.술어에는 다음 세 가지 인수가 전달됩니다.

  1. 키 인덱스은 「」가 ).undefined(이것들)

  2. 가치 그 자체.

  3. JSON 구조의 항목 깊이(최상위 항목의 경우 0).

물론 요건에 따라 필요에 따라 보다 복잡한 술어를 사용할 수도 있습니다.속성 키에 대해 단순 일치를 수행할 경우 술어 함수 대신 문자열 또는 정규식을 전달할 수도 있습니다.

입력 파일을 제어할 수 있고 객체가 배열되어 있으면 이 문제를 더 쉽게 해결할 수 있습니다.다음과 같이 각 레코드가 한 줄에 있는 파일을 출력하도록 배열합니다.

[
   {"key": value},
   {"key": value},
   ...

아직 유효한 JSON입니다.

그런 다음 node.js readline 모듈을 사용하여 한 번에 한 줄씩 처리합니다.

var fs = require("fs");

var lineReader = require('readline').createInterface({
    input: fs.createReadStream("input.txt")
});

lineReader.on('line', function (line) {
    line = line.trim();

    if (line.charAt(line.length-1) === ',') {
        line = line.substr(0, line.length-1);
    }

    if (line.charAt(0) === '{') {
        processRecord(JSON.parse(line));
    }
});

function processRecord(record) {
    // Process the records one at a time here! 
}

는 이 문제를 split npm 모듈을 사용하여 해결했습니다.스트림을 분할하면 "스트림을 분할하고 각 선이 청크가 되도록 재구성합니다."라고 표시됩니다.

샘플 코드:

var fs = require('fs')
  , split = require('split')
  ;

var stream = fs.createReadStream(filePath, {flags: 'r', encoding: 'utf-8'});
var lineStream = stream.pipe(split());
linestream.on('data', function(chunk) {
    var json = JSON.parse(chunk);           
    // ...
});

@josh3736 answer를 사용하지만 ES2021 및 Node.js 16+의 경우 비동기/대기 + AirBnb 규칙을 사용합니다.

import fs from 'node:fs';

const file = 'file.json';

/**
 * @callback itemProcessorCb
 * @param {object} item The current item
 */

/**
 * Process each data chunk in a stream.
 *
 * @param {import('fs').ReadStream} readable The readable stream
 * @param {itemProcessorCb} itemProcessor A function to process each item
 */
async function processChunk(readable, itemProcessor) {
  let data = '';
  let total = 0;

  // eslint-disable-next-line no-restricted-syntax
  for await (const chunk of readable) {
    // join with last result, remove CR and get lines
    const lines = (data + chunk).replace('\r', '').split('\n');

    // clear last result
    data = '';

    // process lines
    let line = lines.shift();
    const items = [];

    while (line) {
      // check if isn't a empty line or an array definition
      if (line !== '' && !/[\[\]]+/.test(line)) {
        try {
          // remove the last comma and parse json
          const json = JSON.parse(line.replace(/\s?(,)+\s?$/, ''));
          items.push(json);
        } catch (error) {
          // last line gets only a partial line from chunk
          // so we add this to join at next loop
          data += line;
        }
      }

      // continue
      line = lines.shift();
    }

    total += items.length;

    // Process items in parallel
    await Promise.all(items.map(itemProcessor));
  }

  console.log(`${total} items processed.`);
}

// Process each item
async function processItem(item) {
  console.log(item);
}

// Init
try {
  const readable = fs.createReadStream(file, {
    flags: 'r',
    encoding: 'utf-8',
  });

  processChunk(readable, processItem);
} catch (error) {
  console.error(error.message);
}

다음과 같은 JSON의 경우:

[
  { "name": "A", "active": true },
  { "name": "B", "active": false },
  ...
]
https.get(url1 , function(response) {
  var data = ""; 
  response.on('data', function(chunk) {
    data += chunk.toString(); 
  }) 
  .on('end', function() {
    console.log(data)
  });
});

데이터베이스를 사용해야 할 것 같아요.이 경우 JSON 호환성이 있기 때문에 MongoDB가 적합합니다.

업데이트: Mongo Import 도구를 사용하여 JSON 데이터를 MongoDB로 가져올 수 있습니다.

mongoimport --collection collection --file collection.json

언급URL : https://stackoverflow.com/questions/11874096/parse-large-json-file-in-nodejs

반응형