Tengo un archivo que almacena muchos objetos JavaScript en formato JSON y necesito leer el archivo, crear cada uno de los objetos y hacer algo con ellos (insertarlos en una base de datos en mi caso). Los objetos JavaScript se pueden representar con un formato:
Formato A:
[{name: 'thing1'},
....
{name: 'thing999999999'}]
o formato B:
{name: 'thing1'} // <== My choice.
...
{name: 'thing999999999'}
Tenga en cuenta que ...
indica muchos objetos JSON. Soy consciente de que podría leer todo el archivo en la memoria y luego usarlo JSON.parse()
así:
fs.readFile(filePath, 'utf-8', function (err, fileContents) {
if (err) throw err;
console.log(JSON.parse(fileContents));
});
Sin embargo, el archivo podría ser muy grande, preferiría usar una secuencia para lograr esto. El problema que veo con una transmisión es que el contenido del archivo podría dividirse en fragmentos de datos en cualquier momento, entonces, ¿cómo puedo usarlo JSON.parse()
en tales objetos?
Idealmente, cada objeto se leería como un fragmento de datos separado, pero no estoy seguro de cómo hacerlo .
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!");
});*/
Tenga en cuenta que deseo evitar leer todo el archivo en la memoria. La eficiencia del tiempo no me importa. Sí, podría intentar leer varios objetos a la vez e insertarlos todos a la vez, pero eso es un ajuste de rendimiento: necesito una forma que esté garantizada para no causar una sobrecarga de memoria, sin importar cuántos objetos estén contenidos en el archivo. .
Puedo elegir usar FormatA
o FormatB
tal vez algo más, solo especifique en su respuesta. ¡Gracias!
fuente
Respuestas:
Para procesar un archivo línea por línea, simplemente necesita desacoplar la lectura del archivo y el código que actúa sobre esa entrada. Puede lograr esto almacenando en búfer su entrada hasta que llegue a una nueva línea. Suponiendo que tenemos un objeto JSON por línea (básicamente, formato B):
Cada vez que el flujo de archivos recibe datos del sistema de archivos, se almacena en un búfer y luego
pump
se llama.Si no hay una nueva línea en el búfer,
pump
simplemente regresa sin hacer nada. Se agregarán más datos (y posiblemente una nueva línea) al búfer la próxima vez que la transmisión obtenga datos, y luego tendremos un objeto completo.Si hay una nueva línea,
pump
corta el búfer desde el principio hasta la nueva línea y se lo pasa aprocess
. Luego vuelve a comprobar si hay otra nueva línea en el búfer (elwhile
bucle). De esta manera, podemos procesar todas las líneas que se leyeron en el fragmento actual.Finalmente,
process
se llama una vez por línea de entrada. Si está presente, elimina el carácter de retorno de carro (para evitar problemas con los finales de línea: LF frente a CRLF) y luego llama aJSON.parse
la línea. En este punto, puede hacer lo que necesite con su objeto.Tenga en cuenta que
JSON.parse
es estricto sobre lo que acepta como entrada; debe citar sus identificadores y valores de cadena con comillas dobles . En otras palabras,{name:'thing1'}
arrojará un error; debe usar{"name":"thing1"}
.Debido a que no habrá más de un fragmento de datos en la memoria a la vez, esto será extremadamente eficiente en la memoria. También será extremadamente rápido. Una prueba rápida mostró que procesé 10,000 filas en menos de 15 ms.
fuente
Justo cuando estaba pensando que sería divertido escribir un analizador JSON de transmisión, también pensé que tal vez debería hacer una búsqueda rápida para ver si ya hay uno disponible.
Resulta que lo hay.
Como lo acabo de encontrar, obviamente no lo he usado, así que no puedo comentar sobre su calidad, pero me interesaría saber si funciona.
Funciona, considere el siguiente Javascript y
_.isString
:Esto registrará los objetos a medida que ingresen si la secuencia es una matriz de objetos. Por lo tanto, lo único que se almacena en búfer es un objeto a la vez.
fuente
A partir de octubre de 2014 , puede hacer algo como lo siguiente (utilizando JSONStream): https://www.npmjs.org/package/JSONStream
Para demostrar con un ejemplo práctico:
data.json:
hola.js:
fuente
parse('*')
o no obtendrá ningún dato.var getStream() = function () {
debe eliminar el primer conjunto de paréntesis .Me doy cuenta de que desea evitar leer todo el archivo JSON en la memoria si es posible, sin embargo, si tiene la memoria disponible, puede que no sea una mala idea en cuanto al rendimiento. El uso de require () de node.js en un archivo json carga los datos en la memoria muy rápido.
Ejecuté dos pruebas para ver cómo se veía el rendimiento al imprimir un atributo de cada característica de un archivo geojson de 81 MB.
En la primera prueba, leí todo el archivo geojson en la memoria usando
var data = require('./geo.json')
. Eso tomó 3330 milisegundos y luego imprimir un atributo de cada función tomó 804 milisegundos para un total de 4134 milisegundos. Sin embargo, parecía que node.js estaba usando 411 MB de memoria.En la segunda prueba, utilicé la respuesta de @ arcseldon con JSONStream + event-stream. Modifiqué la consulta JSONPath para seleccionar solo lo que necesitaba. Esta vez la memoria nunca superó los 82 MB, sin embargo, ¡ahora todo tardó 70 segundos en completarse!
fuente
Tenía un requisito similar, necesito leer un archivo json grande en el nodo js y procesar datos en fragmentos y llamar a una api y guardar en mongodb. inputFile.json es como:
Ahora utilicé JsonStream y EventStream para lograr esto sincrónicamente.
fuente
Escribí un módulo que puede hacer esto, llamado BFJ . Específicamente, el método
bfj.match
se puede usar para dividir un flujo grande en fragmentos discretos de JSON:Aquí,
bfj.match
devuelve una secuencia legible en modo objeto que recibirá los elementos de datos analizados y se le pasan 3 argumentos:Una secuencia legible que contiene el JSON de entrada.
Un predicado que indica qué elementos del JSON analizado se enviarán al flujo de resultados.
Un objeto de opciones que indica que la entrada es JSON delimitado por saltos de línea (esto es para procesar el formato B de la pregunta, no es necesario para el formato A).
Al ser llamado,
bfj.match
analizará JSON desde el flujo de entrada en profundidad primero, llamando al predicado con cada valor para determinar si enviar o no ese elemento al flujo de resultados. Al predicado se le pasan tres argumentos:La clave de propiedad o el índice de matriz (esto será
undefined
para elementos de nivel superior).El valor en sí.
La profundidad del elemento en la estructura JSON (cero para elementos de nivel superior).
Por supuesto, también se puede usar un predicado más complejo según sea necesario según los requisitos. También puede pasar una cadena o una expresión regular en lugar de una función de predicado, si desea realizar coincidencias simples con claves de propiedad.
fuente
Resolví este problema usando el módulo split npm . Canalice su transmisión en división, y "dividirá una transmisión y la volverá a ensamblar para que cada línea sea un fragmento ".
Código de muestra:
fuente
Si tiene control sobre el archivo de entrada y es una matriz de objetos, puede resolver esto más fácilmente. Organice la salida del archivo con cada registro en una línea, así:
Este sigue siendo JSON válido.
Luego, use el módulo readline de node.js para procesarlos una línea a la vez.
fuente
Creo que necesitas usar una base de datos. MongoDB es una buena opción en este caso porque es compatible con JSON.
ACTUALIZACIÓN : puede usar la herramienta mongoimport para importar datos JSON en MongoDB.
fuente