Eu tenho um arquivo que armazena muitos objetos JavaScript no formato JSON e preciso ler o arquivo, criar cada um dos objetos e fazer algo com eles (inseri-los em um banco de dados no meu caso). Os objetos JavaScript podem ser representados em um formato:
Formato A:
[{name: 'thing1'},
....
{name: 'thing999999999'}]
ou Formato B:
{name: 'thing1'} // <== My choice.
...
{name: 'thing999999999'}
Observe que ...
indica muitos objetos JSON. Estou ciente de que posso ler todo o arquivo na memória e usar JSON.parse()
desta forma:
fs.readFile(filePath, 'utf-8', function (err, fileContents) {
if (err) throw err;
console.log(JSON.parse(fileContents));
});
No entanto, o arquivo pode ser muito grande, prefiro usar um fluxo para fazer isso. O problema que vejo com um fluxo é que o conteúdo do arquivo pode ser dividido em blocos de dados a qualquer momento, então, como posso usar JSON.parse()
nesses objetos?
Idealmente, cada objeto seria lido como um bloco de dados separado, mas não tenho certeza de como fazer isso .
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!");
});*/
Observe, desejo evitar a leitura de todo o arquivo na memória. A eficiência do tempo não importa para mim. Sim, eu poderia tentar ler vários objetos de uma vez e inseri-los todos de uma vez, mas isso é um ajuste de desempenho - preciso de uma maneira que garantidamente não cause uma sobrecarga de memória, não importa quantos objetos estejam contidos no arquivo .
Posso escolher usar FormatA
ou FormatB
ou talvez outra coisa, apenas especifique na sua resposta. Obrigado!
fonte
Respostas:
Para processar um arquivo linha por linha, você simplesmente precisa separar a leitura do arquivo e o código que atua sobre essa entrada. Você pode fazer isso armazenando em buffer sua entrada até atingir uma nova linha. Supondo que tenhamos um objeto JSON por linha (basicamente, formato B):
Cada vez que o fluxo de arquivos recebe dados do sistema de arquivos, eles são armazenados em um buffer e
pump
são chamados.Se não houver nova linha no buffer,
pump
simplesmente retorna sem fazer nada. Mais dados (e potencialmente uma nova linha) serão adicionados ao buffer na próxima vez que o fluxo obtiver dados, e então teremos um objeto completo.Se houver uma nova linha,
pump
corta o buffer do início até a nova linha e o passa paraprocess
. Em seguida, verifica novamente se há outra nova linha no buffer (owhile
loop). Dessa forma, podemos processar todas as linhas que foram lidas no trecho atual.Finalmente,
process
é chamado uma vez por linha de entrada. Se estiver presente, ele remove o caractere de retorno de carro (para evitar problemas com terminações de linha - LF vs CRLF) e, em seguida, liga paraJSON.parse
a linha. Neste ponto, você pode fazer o que for necessário com seu objeto.Observe que
JSON.parse
é rígido quanto ao que aceita como entrada; você deve citar seus identificadores e valores de string com aspas duplas . Em outras palavras,{name:'thing1'}
gerará um erro; você deve usar{"name":"thing1"}
.Como não haverá mais do que um pedaço de dados na memória por vez, isso será extremamente eficiente em termos de memória. Também será extremamente rápido. Um teste rápido mostrou que processei 10.000 linhas em menos de 15 ms.
fonte
Assim como eu estava pensando que seria divertido escrever um analisador JSON de streaming, também pensei que talvez devesse fazer uma pesquisa rápida para ver se já existe um disponível.
Acontece que existe.
Como acabei de encontrá-lo, obviamente não o usei, então não posso comentar sobre sua qualidade, mas ficarei interessado em saber se funciona.
Ele funciona considerando o seguinte Javascript e
_.isString
:Isso registrará os objetos à medida que eles entrarem, se o fluxo for uma matriz de objetos. Portanto, a única coisa sendo armazenada em buffer é um objeto por vez.
fonte
A partir de outubro de 2014 , você pode fazer algo como o seguinte (usando JSONStream) - https://www.npmjs.org/package/JSONStream
Para demonstrar com um exemplo de trabalho:
data.json:
hello.js:
fonte
parse('*')
ou não receberá nenhum dado.var getStream() = function () {
deve ser removido.Sei que você deseja evitar a leitura de todo o arquivo JSON na memória, se possível, no entanto, se você tiver memória disponível, pode não ser uma má ideia em termos de desempenho. Usar o require () do node.js em um arquivo json carrega os dados na memória muito rápido.
Executei dois testes para ver como fica o desempenho ao imprimir um atributo de cada recurso de um arquivo geojson de 81 MB.
No primeiro teste, li todo o arquivo geojson na memória usando
var data = require('./geo.json')
. Isso levou 3.330 milissegundos e, em seguida, imprimir um atributo de cada recurso levou 804 milissegundos para um total geral de 4.134 milissegundos. No entanto, parecia que node.js estava usando 411 MB de memória.No segundo teste, usei a resposta de @arcseldon com JSONStream + event-stream. Modifiquei a consulta JSONPath para selecionar apenas o que precisava. Desta vez, a memória nunca ultrapassou 82 MB, no entanto, a coisa toda demorou 70 segundos para ser concluída!
fonte
Eu tinha um requisito semelhante, preciso ler um grande arquivo json no nó js e processar dados em blocos e chamar uma api e salvar no mongodb. inputFile.json é como:
Agora eu usei JsonStream e EventStream para fazer isso de forma síncrona.
fonte
Eu escrevi um módulo que pode fazer isso, chamado BFJ . Especificamente, o método
bfj.match
pode ser usado para quebrar um grande fluxo em blocos discretos de JSON:Aqui,
bfj.match
retorna um fluxo legível no modo de objeto que receberá os itens de dados analisados e recebe 3 argumentos:Um fluxo legível contendo o JSON de entrada.
Um predicado que indica quais itens do JSON analisado serão enviados para o fluxo de resultados.
Um objeto de opções que indica que a entrada é JSON delimitado por nova linha (isso é para processar o formato B da pergunta, não é necessário para o formato A).
Ao ser chamado,
bfj.match
analisará o JSON do fluxo de entrada em profundidade primeiro, chamando o predicado com cada valor para determinar se deve ou não enviar esse item para o fluxo de resultado. O predicado recebe três argumentos:A chave de propriedade ou índice de matriz (será
undefined
para itens de nível superior).O próprio valor.
A profundidade do item na estrutura JSON (zero para itens de nível superior).
É claro que um predicado mais complexo também pode ser usado conforme necessário, de acordo com os requisitos. Você também pode passar uma string ou uma expressão regular em vez de uma função de predicado, se quiser realizar correspondências simples com as chaves de propriedade.
fonte
Resolvi esse problema usando o módulo npm dividido . Divida seu fluxo em uma divisão e " Quebrar um fluxo e remontá-lo de modo que cada linha seja um pedaço ".
Código de amostra:
fonte
Se você tiver controle sobre o arquivo de entrada e for uma matriz de objetos, poderá resolver isso mais facilmente. Organize a saída do arquivo com cada registro em uma linha, assim:
Este ainda é um JSON válido.
Em seguida, use o módulo readline node.js para processá-los uma linha por vez.
fonte
Acho que você precisa usar um banco de dados. MongoDB é uma boa escolha neste caso porque é compatível com JSON.
ATUALIZAÇÃO : você pode usar a ferramenta mongoimport para importar dados JSON para o MongoDB.
fonte