Обработать большой массив объектов в файле JSON [дубликат]

Этот проект в github выглядит многообещающим:

https://github.com/eligrey/FileSaver.js

FileSaver.js реализует интерфейс W3C saveAs () FileSaver в браузерах, которые его не поддерживают.

blockquote>

Также посмотрите здесь демо:

http: / /eligrey.com/demos/FileSaver.js/

70
задан Amol M Kulkarni 13 February 2013 в 13:57
поделиться

9 ответов

Чтобы обрабатывать файл по строкам, вам просто нужно отделить чтение файла и код, который действует на этот вход. Вы можете выполнить это, буферизируя ввод, пока не нажмете новую строку. Предполагая, что у нас есть один объект 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 вызывается один раз для каждой строки ввода. Если он присутствует, он удаляет символ возврата каретки (чтобы избежать проблем с концами строк & ndash; LF vs CRLF), а затем вызывает JSON.parse одну строку. На этом этапе вы можете делать все, что вам нужно, с вашим объектом.

Обратите внимание, что JSON.parse строго относится к тому, что он принимает в качестве входных данных; вы должны указывать ваши идентификаторы и строковые значения с двойными кавычками . Другими словами, {name:'thing1'} выдаст ошибку; вы должны использовать {"name":"thing1"}.

Поскольку за один раз в памяти будет больше, чем кусок данных, это будет чрезвычайно эффективным с точки зрения памяти. Это также будет очень быстро. Быстрый тест показал, что я обработал 10 000 строк в возрасте до 15 мс.

61
ответ дан Kevin B 1 September 2018 в 05:14
поделиться

Так же, как я думал, что было бы интересно написать потоковый анализатор JSON, я также подумал, что, возможно, мне нужно сделать быстрый поиск, чтобы увидеть, есть ли у него уже доступный.

Оказывается, есть

Поскольку я только что нашел его , Я, очевидно, не использовал его, поэтому я не могу прокомментировать его качество, но мне будет интересно услышать, работает ли он.

Он действительно рассматривает следующий CoffeeScript:

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

Это приведет к регистрации объектов по мере их поступления, если поток представляет собой массив объектов. Поэтому единственное, что буферизуется, - это один объект за раз.

28
ответ дан 3 revs, 3 users 46%user1106925 1 September 2018 в 05:14
поделиться

По состоянию на октябрь 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
20
ответ дан arcseldon 1 September 2018 в 05:14
поделиться

Я решил эту проблему, используя модуль 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);           
    // ...
});
3
ответ дан Brian Leathem 1 September 2018 в 05:14
поделиться

Я понимаю, что вы хотите избежать чтения всего JSON-файла в память, если это возможно, однако, если у вас есть доступная память, это может быть плохой идеей. Использование node.js require () в json-файле очень быстро загружает данные в память.

Я провел два теста, чтобы увидеть, как выглядела производительность, при распечатке атрибута из каждой функции из файла geojson 81MB.

В первом тесте я прочитал весь файл geojson в памяти, используя var data = require('./geo.json'). Это заняло 3330 миллисекунд, а затем распечатка атрибута из каждой функции заняла 804 миллисекунды в общей сложности 4134 миллисекунды. Однако оказалось, что node.js использует 411 МБ памяти.

Во втором тесте я использовал ответ @ arcseldon с потоком событий JSONStream +. Я изменил запрос JSONPath, чтобы выбрать только то, что мне нужно. На этот раз память никогда не выходила выше 82 МБ, однако все это заняло 70 секунд!

10
ответ дан Evan Siroky 1 September 2018 в 05:14
поделиться

Я написал модуль, который может это сделать, называется 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 будут перенесены в результат stream.
  3. Объект опций, указывающий на то, что вход представляет собой JSON с разделителем строки (это значит, что формат B должен быть от вопроса, это не требуется для формата A).

При вызове bfj.match будет анализировать JSON из глубины входного потока - сначала, вызывая предикат с каждым значением, чтобы определить, будет ли этот элемент передаваться в поток результатов. Предикат передается по трем аргументам:

  1. Ключ свойства или индекс массива (это будет undefined для элементов верхнего уровня).
  2. Само значение.
  3. Глубина элемента в структуре JSON (ноль для элементов верхнего уровня).

Конечно, более сложный предикат также может быть использован по мере необходимости в соответствии с требованиями. Вы также можете передать строку или регулярное выражение вместо функции предиката, если вы хотите выполнить простые совпадения с ключами свойств.

1
ответ дан Phil Booth 1 September 2018 в 05:14
поделиться

Если у вас есть контроль над входным файлом, и это массив объектов, вы можете решить это более легко. Упорядочивайте вывод файла с каждой записью в одной строке, например:

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

Это все еще действует JSON.

Затем используйте модуль read.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! 
}
1
ответ дан Steve Hanov 1 September 2018 в 05:14
поделиться

У меня было подобное требование, мне нужно прочитать большой json-файл в узле js и обработать данные в кусках и вызвать 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();
    });
}
7
ответ дан Subodh Ghulaxe 1 September 2018 в 05:14
поделиться

Я думаю, вам нужно использовать базу данных. MongoDB является хорошим выбором в этом случае, потому что он совместим с JSON.

UPDATE: вы можете использовать инструмент mongoimport для импорта данных JSON в MongoDB.

mongoimport --collection collection --file collection.json
1
ответ дан Vadim Baryshev 1 September 2018 в 05:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: