नोड्स में बड़ी JSON फ़ाइल पार्स करें


98

मेरे पास एक फाइल है जो JSON फॉर्म में कई जावास्क्रिप्ट ऑब्जेक्ट्स को स्टोर करती है और मुझे फाइल पढ़ने की जरूरत है, प्रत्येक ऑब्जेक्ट को बनाएं, और उनके साथ कुछ करें (उन्हें मेरे मामले में db में डालें)। जावास्क्रिप्ट वस्तुओं को एक प्रारूप का प्रतिनिधित्व किया जा सकता है:

प्रारूप A:

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

या प्रारूप बी:

{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या शायद कुछ और ही है, बस अपने जवाब में स्पष्ट कर दें। धन्यवाद!


प्रारूप बी के लिए आप नई लाइनों के लिए चंक के माध्यम से पार्स कर सकते हैं, और यदि बीच में से कट जाता है, तो बाकी को अलग करते हुए, प्रत्येक पूरी लाइन को निकालें। हालांकि एक और अधिक सुंदर तरीका हो सकता है। मैंने धाराओं के साथ ज्यादा काम नहीं किया है।
ट्रैविस

जवाबों:


82

फ़ाइल लाइन-बाय-लाइन को संसाधित करने के लिए, आपको बस फ़ाइल के पढ़ने और उस इनपुट पर काम करने वाले कोड को डिकूप करने की आवश्यकता है। आप अपने इनपुट को बफ़र करके इसे तब तक पूरा कर सकते हैं जब तक आप एक नई लाइन नहीं मारते। मान लें कि हमारे पास प्रति पंक्ति एक 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इनपुट लाइन के अनुसार एक बार कॉल किया जाता है। यदि मौजूद है, तो यह कैरिज रिटर्न चरित्र (लाइन एंडिंग - एलएफ बनाम सीआरएलएफ के साथ मुद्दों से बचने के लिए) को बंद कर देता है, और फिर JSON.parseएक लाइन को कॉल करता है। इस बिंदु पर, आप अपनी वस्तु के साथ जो भी आवश्यक हो, कर सकते हैं।

ध्यान दें कि JSON.parseयह इनपुट के रूप में क्या स्वीकार करता है, इसके बारे में सख्त है; आपको अपने पहचानकर्ताओं और स्ट्रिंग मानों को दोहरे उद्धरण चिह्नों के साथ उद्धृत करना होगा । दूसरे शब्दों में, {name:'thing1'}एक त्रुटि फेंक देंगे; आप का उपयोग करना चाहिए {"name":"thing1"}

चूँकि डेटा का एक हिस्सा कभी भी एक समय में मेमोरी में नहीं होगा, यह बेहद मेमोरी कुशल होगा। यह भी बेहद तेज होगा। एक त्वरित परीक्षण से पता चला कि मैंने 15 मी के तहत 10,000 पंक्तियों को संसाधित किया।


12
यह जवाब अब बेमानी है। JSONStream का उपयोग करें, और आपके पास बॉक्स समर्थन से बाहर है।
आर्केल्डन

2
फ़ंक्शन नाम 'प्रक्रिया' खराब है। 'प्रक्रिया' एक सिस्टम वेरिएबल होनी चाहिए। इस बग ने मुझे घंटों तक भ्रमित किया।
झीगोंग ली

19
@arcseldon मैं इस तथ्य को नहीं समझता कि एक पुस्तकालय है जो ऐसा करता है जो इस उत्तर को निरर्थक बनाता है। मॉड्यूल के बिना यह कैसे किया जा सकता है, यह जानना निश्चित रूप से अभी भी उपयोगी है।
केविन बी

3
मुझे यकीन नहीं है कि अगर यह एक मिनिस्ड जोंस फाइल के लिए काम करेगा। क्या होगा यदि पूरी फाइल को एक ही लाइन में लपेट दिया गया था, और ऐसे किसी भी सीमांकक का उपयोग करना संभव नहीं था? फिर हम इस समस्या को कैसे हल करेंगे?
SLearner

8
थर्ड पार्टी लाइब्रेरी आपके द्वारा ज्ञात जादू से नहीं बनती हैं। वे इस उत्तर के समान हैं, हाथ से लुढ़का हुआ समाधान के विस्तृत संस्करण, लेकिन सिर्फ एक कार्यक्रम के रूप में पैक और लेबल किया गया है। यह समझना कि चीजें कैसे काम करती हैं, परिणामों की अपेक्षा पुस्तकालय में डेटा फेंकने की तुलना में बहुत अधिक महत्वपूर्ण और प्रासंगिक है। बस कह :)
zanona

35

जैसा कि मैं सोच रहा था कि एक स्ट्रीमिंग JSON पार्सर लिखने में मज़ा आएगा, मैंने यह भी सोचा कि शायद मुझे यह देखने के लिए एक त्वरित खोज करनी चाहिए कि क्या पहले से ही उपलब्ध है।

वहाँ बाहर है।

चूँकि मैंने अभी इसे पाया है, मैंने स्पष्ट रूप से इसका उपयोग नहीं किया है, इसलिए मैं इसकी गुणवत्ता पर टिप्पणी नहीं कर सकता, लेकिन अगर यह काम करता है तो मुझे सुनने में दिलचस्पी होगी।

यह निम्नलिखित जावास्क्रिप्ट पर काम करता है और _.isString:

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

यह ऑब्जेक्ट्स को लॉग करेगा क्योंकि वे आते हैं यदि स्ट्रीम ऑब्जेक्ट की एक सरणी है। इसलिए, केवल एक चीज का शौक होना एक समय में एक वस्तु है।


29

अक्टूबर 2014 तक , आप केवल निम्नलिखित (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.js:

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

2
यह ज्यादातर सच और उपयोगी है, लेकिन मुझे लगता है कि आपको करने की आवश्यकता है parse('*')या आपको कोई डेटा नहीं मिलेगा।
जॉन Zwinck

@JohnZwinck धन्यवाद, ने उत्तर को अपडेट किया है, और इसे पूरी तरह से प्रदर्शित करने के लिए एक कामकाजी उदाहरण जोड़ा है।
आर्केलडॉन

पहले कोड ब्लॉक में, कोष्ठकों का पहला सेट var getStream() = function () {हटा दिया जाना चाहिए।
प्रातःकाल

1
यह 500mb के json फाइल के साथ मेमोरी एरर से फेल हो गया।
कीथ जॉन हचिसन

18

मुझे पता है कि यदि आप संभव हो तो पूरी JSON फाइल को मेमोरी में पढ़ने से बचना चाहते हैं, हालाँकि यदि आपके पास उपलब्ध मेमोरी है तो यह एक बुरा विचार प्रदर्शन-वार नहीं हो सकता है। एक json फ़ाइल पर नोड.जेएस की आवश्यकता () का उपयोग करना डेटा को वास्तव में तेजी से लोड करता है।

मैंने यह देखने के लिए दो परीक्षण किए कि 81MB जियोजोन फ़ाइल से प्रत्येक विशेषता से एक विशेषता को प्रिंट करने पर प्रदर्शन कैसा दिखता है।

पहली परीक्षा में, मैंने पूरी जियोजेन्स फाइल को मेमोरी का उपयोग करके पढ़ा var data = require('./geo.json')। 3330 मिलीसेकंड लिया और फिर प्रत्येक विशेषता से एक विशेषता का मुद्रण करते हुए कुल 4134 मिलीसेकंड के लिए 804 मिलीसेकंड लिया। हालाँकि, यह दिखाई दिया कि नोड .js 411MB मेमोरी का उपयोग कर रहा था।

दूसरे परीक्षण में, मैंने JSONStream + इवेंट-स्ट्रीम के साथ @ arcseldon के उत्तर का उपयोग किया। मैंने JSONPath क्वेरी को केवल वही चुनने के लिए संशोधित किया जो मुझे चाहिए था। इस बार मेमोरी 82MB से अधिक कभी नहीं गई, हालांकि, इस पूरी चीज़ को पूरा होने में अब 70 सेकंड लग गए हैं!


18

मेरे पास समान आवश्यकता थी, मुझे नोड जेएस में एक बड़ी json फाइल पढ़ने और चंक्स में डेटा प्रोसेस करने और एक एपीआई कॉल करने और मोंगोडब में सहेजने की आवश्यकता है। 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();
  });
}

आपका जवाब जोड़ने के लिए बहुत बहुत धन्यवाद, मेरे मामले में भी कुछ समकालिक हैंडलिंग की आवश्यकता थी। हालाँकि परीक्षण के बाद पाइप समाप्त होने के बाद कॉलबैक के रूप में "एंड ()" कॉल करना मेरे लिए संभव नहीं था। मेरा मानना ​​है कि केवल एक चीज जो एक घटना को जोड़ सकती है, धारा only समाप्त ’/ 'क्लोज’ के साथ .fileStream.on (' करीब ’, ...) stream के बाद क्या होना चाहिए।
nonNumericalFloat

6

मैंने एक मॉड्यूल लिखा जो ऐसा कर सकता है, जिसे बीएफजे कहा जाता है । विशेष रूप से, विधि 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 है (यह प्रश्न से प्रारूप बी को संसाधित करने के लिए है, यह प्रारूप ए के लिए आवश्यक नहीं है)।

कहे जाने पर, bfj.matchJSON को इनपुट स्ट्रीम डेप्थ-फर्स्ट से पार्स करेगा, प्रत्येक आइटम के साथ विधेय को यह निर्धारित करने के लिए कि परिणाम आइटम को पुश करने के लिए या नहीं। विधेय को तीन तर्क दिए गए हैं:

  1. संपत्ति कुंजी या सरणी इंडेक्स (यह undefinedशीर्ष स्तर के आइटम के लिए होगा)।

  2. मूल्य ही।

  3. JSON संरचना में आइटम की गहराई (शीर्ष-स्तरीय आइटम के लिए शून्य)।

बेशक एक और अधिक जटिल विधेय भी आवश्यकताओं के अनुसार आवश्यक के रूप में इस्तेमाल किया जा सकता है। यदि आप प्रॉपर्टी कीज़ के खिलाफ सरल मैच करना चाहते हैं, तो आप एक विधेय फ़ंक्शन के बजाय एक स्ट्रिंग या एक नियमित अभिव्यक्ति भी पास कर सकते हैं।


4

मैंने विभाजित 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);           
    // ...
});

4

यदि आपके पास इनपुट फ़ाइल पर नियंत्रण है, और यह वस्तुओं की एक सरणी है, तो आप इसे और अधिक आसानी से हल कर सकते हैं। फ़ाइल को एक पंक्ति में प्रत्येक रिकॉर्ड के साथ आउटपुट करने की व्यवस्था करें, जैसे:

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

यह अभी भी वैध JSON है।

फिर, उन्हें एक बार में एक पंक्ति संसाधित करने के लिए नोड .js रीडलाइन मॉड्यूल का उपयोग करें।

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! 
}

-1

मुझे लगता है कि आपको डेटाबेस का उपयोग करने की आवश्यकता है। MongoDB इस मामले में एक अच्छा विकल्प है क्योंकि यह JSON संगत है।

अद्यतन : आप MongoDB में JSON डेटा आयात करने के लिए mongoimport उपकरण का उपयोग कर सकते हैं ।

mongoimport --collection collection --file collection.json

1
इस सवाल का जवाब नहीं है। ध्यान दें कि प्रश्न की दूसरी पंक्ति कहती है कि वह डेटाबेस में डेटा प्राप्त करने के लिए ऐसा करना चाहती है ।
josh3736

mongoimport केवल 16MB तक फ़ाइल आकार आयात करता है।
हज़ीक अहमद
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.