নোডেজে বড় জেএসএন ফাইল পার্স করুন


101

আমার কাছে একটি ফাইল রয়েছে যা জেএসএন ফর্মে অনেকগুলি জাভাস্ক্রিপ্ট অবজেক্টগুলিকে সঞ্চয় করে এবং আমার ফাইলটি পড়তে হবে, প্রতিটি বস্তু তৈরি করতে হবে এবং সেগুলি দিয়ে কিছু করতে হবে (আমার ক্ষেত্রে এটি একটি ডিবিতে সন্নিবেশ করা উচিত)। জাভাস্ক্রিপ্ট অবজেক্টগুলি একটি ফর্ম্যাট প্রতিনিধিত্ব করতে পারে:

ফর্ম্যাট এ:

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

বা ফর্ম্যাট বি:

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

মনে রাখবেন যে ...জেএসএএন অনেকগুলি বিষয় নির্দেশ করে। আমি সচেতন আমি পুরো ফাইলটি মেমরিতে পড়তে পারি এবং তারপরে এটি ব্যবহার করতে পারি 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!");
});*/

দ্রষ্টব্য, আমি পুরো ফাইলটি মেমোরিতে পড়তে বাধা দিতে চাই। সময়ের দক্ষতা আমার কাছে আসে না। হ্যাঁ, আমি একবারে বেশ কয়েকটি অবজেক্ট পড়ার চেষ্টা করতে পারি এবং সেগুলি একবারে sertোকাতে পারি, তবে এটি একটি পারফরম্যান্সের ঝাঁকুনি - আমার এমন একটি উপায়ের দরকার যা মেমরির ওভারলোডের কারণ না হওয়ার গ্যারান্টিযুক্ত, ফাইলটিতে কতগুলি অবজেক্ট রয়েছে তা বিবেচনা না করেই ।

আমি ব্যবহার করা চয়ন করতে পারেন FormatAবা FormatBহয়তো বা অন্য কিছু, শুধু আপনার উত্তর উল্লেখ করুন। ধন্যবাদ!


বি বিন্যাসের জন্য আপনি নতুন লাইনের জন্য অংশটি বিশ্লেষণ করতে পারবেন এবং প্রতিটি পুরো লাইনটি বের করতে পারবেন, যদি মাঝখানে বন্ধ হয়ে যায় তবে বাকী অংশটি যুক্ত করে। যদিও আরও মার্জিত উপায় হতে পারে। আমি স্ট্রিমের সাথে খুব বেশি কাজ করি নি।
ট্র্যাভিস

উত্তর:


83

একটি লাইন বাই লাইন ফাইল প্রক্রিয়া করার জন্য, আপনাকে কেবল ফাইলটি পড়তে এবং সেই ইনপুটটিতে কাজ করা কোডটি ডিকুয়াল করা দরকার। আপনি কোনও নতুন লাইনে না আসা পর্যন্ত আপনি নিজের ইনপুটটি বাফার করে এটি সম্পাদন করতে পারেন। ধরে নিচ্ছি আমাদের প্রতি লাইনে একটি জেএসএন অবজেক্ট রয়েছে (মূলত, বি ফর্ম্যাট করুন):

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
এই উত্তরটি এখন অপ্রয়োজনীয়। জেএসএনস্ট্রিম ব্যবহার করুন এবং আপনার বাক্স সমর্থনটি শেষ।
আর্সেল্ডন

4
ফাংশনটির নাম 'প্রক্রিয়া' খারাপ। 'প্রক্রিয়া' সিস্টেমের পরিবর্তনশীল হওয়া উচিত। এই বাগ আমাকে কয়েক ঘন্টা ধরে বিভ্রান্ত করে।
Zhigong Li

21
@ কারসেল্ডন আমি মনে করি না যে একটি লাইব্রেরি রয়েছে যা এই উত্তরটিকে অনর্থক করে তোলে। এটি মডিউল ব্যতীত কীভাবে করা যায় তা জেনে রাখা এখনও অবশ্যই দরকারী।
কেভিন বি

4
আমি নিশ্চিত নই যে এটি কোনও মিনিফাইড জেসন ফাইলের জন্য কাজ করবে কিনা। পুরো ফাইলটি যদি একক লাইনে মুড়ে ফেলা হয়, এবং এই জাতীয় কোনও ডিলিমিটার ব্যবহার করা সম্ভব না হয় তবে কী হবে? তখন কীভাবে আমরা এই সমস্যাটি সমাধান করব?
স্লিয়ারার

10
তৃতীয় পক্ষের গ্রন্থাগারগুলি আপনি জানেন এমন যাদু দ্বারা তৈরি করা হয় না। তারা ঠিক এই উত্তরটির মতো, হাত-ঘূর্ণিত সমাধানগুলির বিশদ বিবরণ তবে কেবল প্যাকড এবং একটি প্রোগ্রাম হিসাবে লেবেলযুক্ত। ফলাফলগুলি প্রত্যাশিত লাইব্রেরিতে অন্ধভাবে ডেটা ফেলে দেওয়ার চেয়ে বিষয়গুলি কীভাবে কাজ করে তা বোঝা অনেক বেশি গুরুত্বপূর্ণ এবং প্রাসঙ্গিক। শুধু বলছি :)
জ্যানোনা

36

আমি যেমন ভাবছিলাম যে স্ট্রিমিং জেএসএন পার্সার লিখতে মজা পাবে, আমিও ভেবেছিলাম যে ইতিমধ্যে কোনও উপলব্ধ আছে কিনা তা দেখার জন্য আমার দ্রুত অনুসন্ধান করা উচিত।

সেখানে দেখা যাচ্ছে।

যেহেতু আমি এটি সবেমাত্র পেয়েছি, আমি স্পষ্টতই এটি ব্যবহার করি নি, সুতরাং আমি এর গুণমান সম্পর্কে মন্তব্য করতে পারি না, তবে এটি কাজ করে কিনা তা শুনতে আমি আগ্রহী।

এটি নিম্নলিখিত জাভাস্ক্রিপ্ট বিবেচনা করে কাজ করে _.isString:

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

যদি স্ট্রিমটি অবজেক্টগুলির একটি অ্যারে হয় তবে এটি বস্তুগুলিতে আসার সাথে সাথে তাদের লগ করবে। অতএব কেবল বাফার করা হচ্ছে এক সময় এক জিনিস।


30

অক্টোবর ২০১৪ অবধি, আপনি ঠিক নীচের মতো কিছু করতে পারেন (জেএসএনস্ট্রিম ব্যবহার করে) - 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

ডাটা.জসন:

{
  "greeting": "hello world"
}

হ্যালো.জেএস:

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

4
এটি বেশিরভাগ ক্ষেত্রে সত্য এবং দরকারী, তবে আমি মনে করি আপনাকে এটি করা দরকার parse('*')বা আপনি কোনও ডেটা পাবেন না।
জন জুইন্ক

@ জোহানজুইনক আপনাকে ধন্যবাদ, উত্তরটি আপডেট করেছেন এবং এটি পুরোপুরি প্রদর্শনের জন্য একটি কার্যকারী উদাহরণ যোগ করেছেন।
আর্সেল্ডন

প্রথম কোড ব্লকে, প্রথম বন্ধনীর প্রথম সেটটি var getStream() = function () {সরানো উচিত।
গেমসনে্যাকস

4
এটি 500 এমবি জেসন ফাইলের সাথে মেমরির ত্রুটির বাইরে ব্যর্থ হয়েছিল।
কিথ জন হাচিসন

18

আমি বুঝতে পেরেছি যে আপনি যদি সম্ভব হয় তবে পুরো JSON ফাইলটি মেমোরিতে পড়তে বাড়াতে চান, তবে আপনার কাছে মেমরিটি উপলব্ধ থাকলে এটি কোনও খারাপ ধারণা কর্মক্ষমতা অনুযায়ী হতে পারে না। একটি জসন ফাইলে নোড.জেএস এর প্রয়োজনীয়তা () ব্যবহার করা ডেটা মেমরির মধ্যে দ্রুত লোড করে।

আমি ৮১ এমবি জিওজসন ফাইল থেকে প্রতিটি বৈশিষ্ট্য থেকে কোনও বৈশিষ্ট্য ছাপানোর ক্ষেত্রে পারফরম্যান্সটি দেখতে কেমন তা দেখতে দুটি পরীক্ষা চালিয়েছি।

1 ম পরীক্ষায়, আমি সম্পূর্ণ জিওজসন ফাইলটি মেমরি ব্যবহার করে পড়েছি var data = require('./geo.json') । এটি ৩৩৩০ মিলিসেকেন্ড নিয়েছে এবং তারপরে প্রতিটি বৈশিষ্ট্য থেকে একটি গুণ ছাপতে 41০৪ মিলি সেকেন্ডের গ্র্যান্ডস টেল ৪১৪৩ মিলিসেকেন্ডের জন্য নিয়েছে। তবে এটি উপস্থিত হয়েছিল যে নোড.জেএস 411 এমবি মেমরি ব্যবহার করে।

দ্বিতীয় পরীক্ষায়, আমি জেএসএনস্ট্রিম + ইভেন্ট-স্ট্রিমের সাথে @ আরসেল্ডনের উত্তর ব্যবহার করেছি। আমি যা প্রয়োজন কেবল তা চয়ন করতে আমি JSONPath কোয়েরিটি পরিবর্তন করেছি। এবার মেমরিটি কখনই M২ এমবি এর চেয়ে বেশি যায় নি, তবে পুরো জিনিসটি এখন শেষ হতে seconds০ সেকেন্ড সময় নিয়েছে!


18

আমার অনুরূপ প্রয়োজনীয়তা ছিল, আমার নোড জেএসে একটি বৃহত জসন ফাইল পড়তে হবে এবং খণ্ডগুলিতে ডেটা প্রসেস করতে হবে এবং এপিআই কল করতে হবে এবং মংডোবে সংরক্ষণ করতে হবে। ইনপুটফিল.জসনের মতো:

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

এখন আমি সিঙ্ক্রোনালি এটি অর্জনের জন্য জসনস্ট্রিম এবং ইভেন্টস্ট্রিম ব্যবহার করেছি।

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();
  });
}

আপনার উত্তর যুক্ত করার জন্য আপনাকে অনেক ধন্যবাদ, আমার ক্ষেত্রেও কিছু সংক্রামক হ্যান্ডলিং দরকার। তবে পরীক্ষার পরে পাইপ শেষ হওয়ার পরে আমার পক্ষে "শেষ ()" কলব্যাক হিসাবে কল করা সম্ভব হয়নি। আমি বিশ্বাস করি যে একমাত্র কাজটি ঘটতে পারে তা হল একটি ইভেন্ট যোগ করা, ilefileStream.on ('ক্লোজ', ...) with দিয়ে 'সমাপ্ত' / 'বন্ধ' হওয়ার পরে কী হওয়া উচিত after

6

আমি এমন একটি মডিউল লিখেছিলাম যা এটি করতে পারে, তাকে বিএফজে বলে । বিশেষত, পদ্ধতিটি bfj.matchJSON এর বিচ্ছিন্ন অংশগুলিতে একটি বৃহত ধারা প্রবাহিত করতে ব্যবহৃত হতে পারে:

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. এমন একটি শিকারী যা ইঙ্গিত দেয় যে পার্স করা জেএসওএন থেকে কোন আইটেম ফলাফলের প্রবাহে ঠেলা যাবে।

  3. একটি বিকল্পের অবজেক্টটি ইঙ্গিত করে যে ইনপুটটি নিউলাইন-বিস্মৃত জেএসওএন (এটি প্রশ্ন থেকে ফর্ম্যাট বি প্রক্রিয়াকরণের জন্য এটি এ বিন্যাসের প্রয়োজন হয় না)।

ডাকা হওয়ার পরে, bfj.matchJSON কে ইনপুট স্ট্রিমের গভীরতা-প্রথম থেকে বিশ্লেষণ করবে, ফলাফলটিকে প্রবাহের ধরণে আইটেমটি ধাক্কা দিতে হবে কি না তা নির্ধারণের জন্য প্রতিটি মান দিয়ে প্রিকেটকে কল করবে। শিকারী তিনটি আর্গুমেন্ট পাস:

  1. সম্পত্তি কী বা অ্যারে সূচক (এটি undefinedশীর্ষ স্তরের আইটেমগুলির জন্য হবে )।

  2. মান নিজেই।

  3. জেএসএন কাঠামোর আইটেমটির গভীরতা (শীর্ষ স্তরের আইটেমগুলির জন্য শূন্য)।

অবশ্যই আরও জটিল শিকারী প্রয়োজন হিসাবে প্রয়োজনীয় হিসাবে ব্যবহার করা যেতে পারে। আপনি যদি সম্পত্তি কীগুলির সাথে সহজ ম্যাচগুলি করতে চান তবে আপনি প্রেডিকেট ফাংশনটির পরিবর্তে একটি স্ট্রিং বা একটি রেগুলার এক্সপ্রেশনও পাশ করতে পারেন।


4

আমি বিভক্ত এনপিএম মডিউলটি ব্যবহার করে এই সমস্যাটি সমাধান করেছি । আপনার স্ট্রিমটিকে বিভক্ত করে তুলুন এবং এটি " একটি স্ট্রিম ভেঙে আবার পুনরায় মিলিয়ে দেবে যাতে প্রতিটি লাইন একটি অংশ হয়ে যায় "।

কোডের উদাহরণ:

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।

তারপরে নোড.জেএস রিডলাইন মডিউলটি একবারে এক লাইনে প্রক্রিয়া করতে ব্যবহার করুন।

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

আমার মনে হয় আপনার একটি ডাটাবেস ব্যবহার করা দরকার। মোঙ্গোডিবি এই ক্ষেত্রে একটি ভাল পছন্দ কারণ এটি জেএসএন সামঞ্জস্যপূর্ণ।

আপডেট : আপনি মঙ্গোডিবিতে জেএসএন ডেটা আমদানি করতে মঙ্গোইম্পোর্ট সরঞ্জামটি ব্যবহার করতে পারেন ।

mongoimport --collection collection --file collection.json

4
এটি প্রশ্নের উত্তর দেয় না। নোট করুন যে প্রশ্নের দ্বিতীয় লাইনে বলা হয়েছে যে একটি ডাটাবেসে ডেটা পেতে তিনি এটি করতে চান ।
josh3736

মঙ্গোইম্পোর্ট কেবল 16 এমবি পর্যন্ত ফাইলের আকার আমদানি করে।
হাজিক আহমেদ 16'18
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.