我有一个以JSON格式存储许多JavaScript对象的文件,我需要读取文件,创建每个对象,并对它们执行某些操作(在我的情况下将它们插入到db中)。 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
或其他内容,请在答案中注明。谢谢!
答案 0 :(得分:68)
要逐行处理文件,您只需要解除文件的读取和作用于该输入的代码。您可以通过缓冲输入直到您换到换行符来完成此操作。假设我们每行有一个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
。如果存在,它将去除回车符(以避免行结尾问题 - LF与CRLF),然后调用JSON.parse
一行。此时,您可以对对象执行任何操作。
请注意JSON.parse
对其接受的输入是严格的;您必须使用双引号引用标识符和字符串值。换句话说,{name:'thing1'}
会抛出错误;你必须使用{"name":"thing1"}
。
因为一次只有一块数据存在于内存中,这将极其节省内存。它也会非常快。快速测试显示我在15ms内处理了10,000行。
答案 1 :(得分:29)
正如我认为编写流式JSON解析器会很有趣,我也认为我应该快速搜索一下是否有可用的。
原来有。
由于我刚刚发现它,我显然没有使用它,所以我无法评论它的质量,但我会有兴趣听听它是否有效。
确实可以考虑以下CoffeeScript:
stream.pipe(JSONStream.parse('*'))
.on 'data', (d) ->
console.log typeof d
console.log "isString: #{_.isString d}"
如果对象是一个对象数组,它将在对象进入时记录它们。因此,唯一缓冲的是一次一个对象。
答案 2 :(得分:23)
截至2014年10月,您可以执行以下操作(使用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
答案 3 :(得分:11)
我有类似的要求,我需要读取节点js中的大型json文件并以块的形式处理数据并调用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();
});
}
答案 4 :(得分:4)
我使用split npm module解决了这个问题。将您的流管道分割,它将“分解流并重新组合它,以便每行都是一个块”。
示例代码:
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);
// ...
});
答案 5 :(得分:3)
如果您可以控制输入文件,并且它是一个对象数组,则可以更轻松地解决此问题。安排在一行输出包含每条记录的文件,如下所示:
[
{"key": value},
{"key": value},
...
这仍然是有效的JSON。
然后,使用node.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!
}
答案 6 :(得分:3)
我写了一个可以做到这一点的模块,名为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个参数:
包含输入JSON的可读流。
一个谓词,指示解析的JSON中的哪些项目将被推送到结果流。
一个options对象,指示输入是换行符分隔的JSON(这是从问题处理格式B,格式A不需要它)。
在被调用时,bfj.match
将从输入流深度优先解析JSON,使用每个值调用谓词以确定是否将该项目推送到结果流。谓词传递三个参数:
属性键或数组索引(对于顶级项目,这将是undefined
。)
价值本身。
JSON结构中项目的深度(顶级项目为零)。
当然,根据需要,也可以根据需要使用更复杂的谓词。如果要对属性键执行简单匹配,也可以传递字符串或正则表达式而不是谓词函数。
答案 7 :(得分:0)
我认为您需要使用数据库。在这种情况下,MongoDB是一个不错的选择,因为它与JSON兼容。
<强>更新强>: 您可以使用mongoimport工具将JSON数据导入MongoDB。
mongoimport --collection collection --file collection.json