Этот проект в github выглядит многообещающим:
https://github.com/eligrey/FileSaver.js
FileSaver.js реализует интерфейс W3C saveAs () FileSaver в браузерах, которые его не поддерживают.
blockquote>Также посмотрите здесь демо:
Чтобы обрабатывать файл по строкам, вам просто нужно отделить чтение файла и код, который действует на этот вход. Вы можете выполнить это, буферизируя ввод, пока не нажмете новую строку. Предполагая, что у нас есть один объект 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 мс.
Так же, как я думал, что было бы интересно написать потоковый анализатор JSON, я также подумал, что, возможно, мне нужно сделать быстрый поиск, чтобы увидеть, есть ли у него уже доступный.
Оказывается, есть
Поскольку я только что нашел его , Я, очевидно, не использовал его, поэтому я не могу прокомментировать его качество, но мне будет интересно услышать, работает ли он.
Он действительно рассматривает следующий CoffeeScript:
stream.pipe(JSONStream.parse('*'))
.on 'data', (d) ->
console.log typeof d
console.log "isString: #{_.isString d}"
Это приведет к регистрации объектов по мере их поступления, если поток представляет собой массив объектов. Поэтому единственное, что буферизуется, - это один объект за раз.
По состоянию на октябрь 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
Я решил эту проблему, используя модуль 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);
// ...
});
Я понимаю, что вы хотите избежать чтения всего JSON-файла в память, если это возможно, однако, если у вас есть доступная память, это может быть плохой идеей. Использование node.js require () в json-файле очень быстро загружает данные в память.
Я провел два теста, чтобы увидеть, как выглядела производительность, при распечатке атрибута из каждой функции из файла geojson 81MB.
В первом тесте я прочитал весь файл geojson в памяти, используя var data = require('./geo.json')
. Это заняло 3330 миллисекунд, а затем распечатка атрибута из каждой функции заняла 804 миллисекунды в общей сложности 4134 миллисекунды. Однако оказалось, что node.js использует 411 МБ памяти.
Во втором тесте я использовал ответ @ arcseldon с потоком событий JSONStream +. Я изменил запрос JSONPath, чтобы выбрать только то, что мне нужно. На этот раз память никогда не выходила выше 82 МБ, однако все это заняло 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 аргумента:
При вызове bfj.match
будет анализировать JSON из глубины входного потока - сначала, вызывая предикат с каждым значением, чтобы определить, будет ли этот элемент передаваться в поток результатов. Предикат передается по трем аргументам:
undefined
для элементов верхнего уровня). Конечно, более сложный предикат также может быть использован по мере необходимости в соответствии с требованиями. Вы также можете передать строку или регулярное выражение вместо функции предиката, если вы хотите выполнить простые совпадения с ключами свойств.
Если у вас есть контроль над входным файлом, и это массив объектов, вы можете решить это более легко. Упорядочивайте вывод файла с каждой записью в одной строке, например:
[
{"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!
}
У меня было подобное требование, мне нужно прочитать большой 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();
});
}
Я думаю, вам нужно использовать базу данных. MongoDB является хорошим выбором в этом случае, потому что он совместим с JSON.
UPDATE: вы можете использовать инструмент mongoimport для импорта данных JSON в MongoDB.
mongoimport --collection collection --file collection.json