145 votes

Parse grand fichier JSON à Nodejs

J'ai un fichier qui stocke de nombreux objets JavaScript, JSON forme et j'ai besoin de lire le fichier, de créer chacun de ces objets, et de faire quelque chose avec eux (les insérer dans une base de données dans mon cas). Les objets JavaScript peut être représenté un format:

Format A:

[{name: 'thing1'},
....
{name: 'thing999999999'}]

ou Format B:

{name: 'thing1'}         // <== My choice.
...
{name: 'thing999999999'}

Notez que l' ... indique un lot d'objets JSON. Je suis conscient que je pouvais lire tout le fichier en mémoire et ensuite utiliser JSON.parse() comme ceci:

fs.readFile(filePath, 'utf-8', function (err, fileContents) {
  if (err) throw err;
  console.log(JSON.parse(fileContents));
});

Toutefois, le fichier pourrait être vraiment grand, je préfère utiliser un flux pour accomplir cette tâche. Le problème que je vois avec un ruisseau, c'est que le contenu du fichier peut être brisé en morceaux de données à tout moment, alors comment puis-je utiliser JSON.parse() sur de tels objets?

Idéalement, chaque objet serait de lire séparément les données de morceau, mais je ne suis pas sûr de la façon de le faire.

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!");
});*/

Remarque, je tiens à prévenir la lecture de la totalité du fichier en mémoire. L'efficacité dans le temps n'a pas d'importance pour moi. Oui, je pourrais essayer de lire un certain nombre d'objets à la fois et de les insérer tous à la fois, mais c'est une performance tweak - je besoin d'une manière qui est garanti de ne pas provoquer une surcharge de la mémoire, pas d'importance combien d'objets sont contenus dans le fichier.

Je peux choisir d'utiliser FormatA ou FormatB ou peut-être quelque chose d'autre, veuillez préciser dans votre réponse. Merci!

101voto

josh3736 Points 41911

Pour traiter un fichier ligne par ligne, vous simplement besoin de les dissocier de la lecture du fichier et le code qui agit sur entrée. Vous pouvez accomplir ceci en mémoire tampon de votre entrée jusqu'à ce que vous frappez un saut de ligne. En supposant que nous avons un objet JSON par ligne (en gros, le format 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
        }
        process(buf.slice(0,pos)); // hand off the line
        buf = buf.slice(pos+1); // and slice the processed data off the buffer
    }
}

function process(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!
    }
}

Chaque fois que le flux de fichier reçoit des données à partir du système de fichiers, il est planqué dans un tampon, puis pump est appelé.

Si il n'y a pas de saut de ligne dans le tampon, pump retourne simplement sans rien faire. Plus de données (et potentiellement un retour à la ligne) sera ajouté au tampon, la prochaine fois que le flux récupère les données, et ensuite, nous aurons un objet complet.

Si il y a un saut de ligne, pump tranches au large de la mémoire tampon à partir du début de la nouvelle ligne et les mains à process. Ensuite, il vérifie à nouveau si il y a un autre saut de ligne dans le tampon (la while boucle). De cette façon, nous pouvons traiter toutes les lignes qui ont été lus dans le segment actuel.

Enfin, process est appelée une seule fois par ligne de saisie. S'il est présent, il enlève le caractère de retour de chariot (pour éviter les problèmes, avec des fins de ligne – LF vs CRLF), puis appelle JSON.parse un la ligne. À ce stade, vous pouvez faire ce que vous devez à votre objet.

Notez que JSON.parse est stricte sur ce qu'il accepte comme entrée; vous devez indiquer vos identifiants et des valeurs de chaîne entre guillemets. En d'autres termes, {name:'thing1'} lèvera une erreur; vous devez utiliser {"name":"thing1"}.

Parce que rien de plus qu'un bloc de données ne sera jamais dans la mémoire à un moment, cela va être extrêmement efficace en terme de mémoire. Il sera également extrêmement rapide. Un test rapide a montré de me traiter 10 000 lignes en vertu de 15ms.

48voto

squint Points 28293

Tout comme je pensais que ce serait amusant d'écrire un parser JSON en streaming, j'ai aussi pensé que peut-être je devrais faire une recherche rapide pour voir s'il ya un déjà disponible.

Il s'avère qu'il y en a.

  • JSONStream "streaming JSON.parse and stringify" JSONStream "streaming JSON.parse and stringify" JSONStream "streaming JSON.parse and stringify" JSON

Depuis que je viens de le trouver, je n'ai évidemment pas utilisé, donc je ne peux pas commenter sur sa qualité, mais je serai intéressé d'entendre si cela fonctionne.

41voto

arcseldon Points 400

En octobre 2014, vous pouvez tout simplement faire quelque chose comme ce qui suit (à l'aide de 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
     });

Mise à jour:

Pour démontrer, avec un exemple:

npm install JSONStream événement-stream

les données.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);
  }));

nœud hello.js

bonjour tout le monde

1voto

Amol M Kulkarni Points 4105

En utilisant votre fichier enregistré par format `` ; Vous pouvez simplement avoir besoin d'un fichier json (c.-à-d. fichier avec extension .json)

Pas besoin de suivre toutes les voies rondes.

Bonne exploration... :-)

-8voto

Vadim Baryshev Points 8162

Je pense que vous devez utiliser une base de données. MongoDB est un bon choix dans ce cas car il est compatible JSON.

MISE À JOUR: Vous pouvez utiliser l'outil mongoimport pour importer des données JSON dans MongoDB.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X