У меня есть файл, в котором хранится множество объектов JavaScript в форме JSON, и мне нужно прочитать файл, создать каждый из объектов и что-то с ними сделать (в моем случае вставьте их в базу данных). Объекты 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
может быть, что-то еще, просто укажите в своем ответе. Спасибо!
Ответы:
Чтобы обработать файл построчно, вам просто нужно разделить чтение файла и код, который действует на этот ввод. Вы можете добиться этого, буферизовав ввод, пока не достигнете новой строки. Предполагая, что у нас есть один объект JSON на строку (в основном, формат B):
Каждый раз, когда файловый поток получает данные из файловой системы, они сохраняются в буфере, а затем
pump
вызываются.Если в буфере нет новой строки,
pump
просто возвращается, ничего не делая. Дополнительные данные (и, возможно, новая строка) будут добавлены в буфер в следующий раз, когда поток получит данные, и тогда у нас будет полный объект.Если есть новая строка,
pump
отрезает буфер от начала до новой строки и передает егоprocess
. Затем он снова проверяет, есть ли еще одна новая строка в буфере (while
цикл). Таким образом мы можем обработать все строки, которые были прочитаны в текущем блоке.Наконец,
process
вызывается один раз для каждой строки ввода. Если он присутствует, он удаляет символ возврата каретки (чтобы избежать проблем с окончанием строки - LF против CRLF), а затем вызываетJSON.parse
одну строку. На этом этапе вы можете делать со своим объектом все, что вам нужно.Обратите внимание, что
JSON.parse
это строго в отношении того, что он принимает в качестве входных данных; вы должны заключать свои идентификаторы и строковые значения в двойные кавычки . Другими словами,{name:'thing1'}
выдаст ошибку; вы должны использовать{"name":"thing1"}
.Поскольку одновременно в памяти может находиться не более одного фрагмента данных, это будет чрезвычайно эффективно с точки зрения памяти. Это также будет очень быстро. Быстрый тест показал, что я обработал 10 000 строк менее чем за 15 мс.
источник
Так же, как я думал, что было бы весело написать потоковый парсер JSON, я также подумал, что, возможно, мне следует выполнить быстрый поиск, чтобы увидеть, доступен ли он уже.
Оказывается, есть.
Поскольку я только что нашел его, я, очевидно, не использовал его, поэтому я не могу комментировать его качество, но мне будет интересно узнать, работает ли он.
Это действительно работает с учетом следующего Javascript и
_.isString
:Это будет регистрировать объекты по мере их поступления, если поток является массивом объектов. Следовательно, буферизируется только один объект за раз.
источник
По состоянию на октябрь 2014 года вы можете делать что-то вроде следующего (используя JSONStream) - https://www.npmjs.org/package/JSONStream
Чтобы продемонстрировать на рабочем примере:
data.json:
hello.js:
источник
parse('*')
иначе вы не получите никаких данных.var getStream() = function () {
следует убрать первый набор скобок .Я понимаю, что вы хотите по возможности избегать чтения всего файла JSON в память, однако, если у вас есть доступная память, это может быть неплохой идеей с точки зрения производительности. Использование node.js require () в json-файле очень быстро загружает данные в память.
Я провел два теста, чтобы посмотреть, как выглядит производительность при распечатке атрибута каждой функции из файла geojson размером 81 МБ.
В первом тесте я прочитал весь файл geojson в память, используя
var data = require('./geo.json')
. Это заняло 3330 миллисекунд, а затем распечатка атрибута каждой функции заняла 804 миллисекунды, что в целом составляет 4134 миллисекунды. Однако оказалось, что node.js использует 411 МБ памяти.Во втором тесте я использовал ответ @ arcseldon с потоком событий JSONStream +. Я изменил запрос JSONPath, чтобы выбрать только то, что мне нужно. На этот раз объем памяти никогда не превышал 82 МБ, однако теперь все это заняло 70 секунд!
источник
У меня было аналогичное требование: мне нужно прочитать большой файл json в узле js и обработать данные кусками, вызвать api и сохранить в mongodb. inputFile.json похож на:
Теперь я использовал JsonStream и EventStream, чтобы добиться этого синхронно.
источник
Я написал модуль, который может это сделать, под названием BFJ . В частности, этот метод
bfj.match
можно использовать для разбиения большого потока на отдельные фрагменты JSON:Здесь
bfj.match
возвращается читаемый поток объектного режима, который будет получать проанализированные элементы данных, и ему передаются 3 аргумента:Читаемый поток, содержащий входной JSON.
Предикат, указывающий, какие элементы из проанализированного JSON будут отправлены в поток результатов.
Объект параметров, указывающий, что ввод - это JSON с разделителями новой строки (это необходимо для обработки формата B из вопроса, это не требуется для формата A).
После
bfj.match
вызова будет анализировать JSON из входного потока в глубину, вызывая предикат с каждым значением, чтобы определить, нужно ли отправлять этот элемент в поток результатов. Предикату передается три аргумента:Ключ свойства или индекс массива (это будет
undefined
для элементов верхнего уровня).Сама стоимость.
Глубина элемента в структуре JSON (ноль для элементов верхнего уровня).
Конечно, в зависимости от требований при необходимости можно использовать и более сложный предикат. Вы также можете передать строку или регулярное выражение вместо функции предиката, если хотите выполнять простые сопоставления с ключами свойств.
источник
Я решил эту проблему с помощью модуля split npm . Разделите свой поток на split, и он «разбивает поток и собирает его так, чтобы каждая строка была фрагментом ».
Образец кода:
источник
Если у вас есть контроль над входным файлом, и это массив объектов, вам будет проще решить эту проблему. Организуйте вывод файла с каждой записью в одной строке, например:
Это все еще действующий JSON.
Затем используйте модуль readline node.js, чтобы обрабатывать их по одной строке за раз.
источник
Я думаю, вам нужно использовать базу данных. В этом случае MongoDB - хороший выбор, поскольку он совместим с JSON.
ОБНОВЛЕНИЕ : вы можете использовать инструмент mongoimport для импорта данных JSON в MongoDB.
источник