all files / lib/ json-sieve.js

100% Statements 42/42
88% Branches 22/25
100% Functions 5/5
100% Lines 42/42
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112                                                          12× 12×         16× 16×           16× 16× 16× 16× 21× 21× 21×   16× 16× 13×               24×       24× 24×   24×           21×             17×         12×   12×        
// ## JSONSieve
//
// Picks out line-delimited JSON arrays and objects in a data stream, and
// parses the rest into lines.
//
// ### Usage
//
//     var sieve = new JSONSieve();
//     process.stdin.on('data', function(chunk) {
//       sieve.observe(chunk);
//     });
//     process.stdin.on('end', function() {
//       sieve.close();  // important
//     });
//     sieve.on('stdout_line', ...);
//     sieve.on('stderr_data', ...);
//     sieve.on('json_object', ...);
//     sieve.on('json_array', ...);
//
// `JSONSieve` is an `EventEmitter`.
//
// __Warning:__ Note that JSON will not be parsed if it
// is "pretty-printed" with whitespace.
//
// ### Source
var EventEmitter = require('events')
  , util = require('util')
  ;
 
 
function JSONSieve() {
  EventEmitter.call(this);
  this.lineBuffer_ = '';
}
 
util.inherits(JSONSieve, EventEmitter);
 
// After construction, chunked data may be passed to `observe()`.
JSONSieve.prototype.observe = function(dataStr) {
  this.lineBuffer_ += dataStr;
  this.parseLineBuffer_();
};
 
// On every chunk, we add the new data to the existing line buffer,
// and attempting to find newlines to split on. If there are no newlines,
// then assume that the last chunk we got was a partial line.
JSONSieve.prototype.parseLineBuffer_ = function() {
  var lines = [];
  var dataStr = this.lineBuffer_;
  var idx = dataStr.indexOf('\n');
  while(idx >= 0) {
    lines.push(dataStr.substr(0, idx));  // Excludes terminating new line.
    dataStr = dataStr.substr(idx + 1);   // Skip newline.
    idx = dataStr.indexOf('\n');
  }
  this.lineBuffer_ = dataStr;            // Linebuffer contains leftovers.
  if (lines.length) {
    lines.forEach(this.processLine_, this);
  }
};
 
// For every line parsed out of the stream, attempt to decode JSON strings
// and arrays. A decision is made here not to attempt to decode JSON strings
// and numbers, because they are too accidentally common in most output streams.
JSONSieve.prototype.processLine_ = function(line) {
  // Allow some fuzz in the input in the form of leading and trailing whitespace.
  var trimmedLine = line.replace(/^[\s\u0000-\u001f]*|[\s\u0000-\u001f]*$/g, '');
 
  // Check for necessary conditions for line-delimited JSON objects and arrays,
  // even empty ones.
  var len = trimmedLine.length;
  Eif (len >= 2) {
    // Test for possible line-delimited JSON objects.
    if (trimmedLine[0] === '{' && trimmedLine[len - 1] === '}') {
      var jsonObj;
      try {
        jsonObj = JSON.parse(trimmedLine);
      } catch(e) { }  // Nope, not a JSON object.
      Eif (jsonObj && typeof jsonObj == 'object') {
        return this.emit('json_object', jsonObj);
      }
    }
 
    // Test for possible line-delimited JSON arrays.
    if (trimmedLine[0] === '[' && trimmedLine[len - 1] === ']') {
      var jsonArr;
      try {
        jsonArr = JSON.parse(trimmedLine);
      } catch(e) { }  // Nope, not a JSON array.
      Eif (jsonArr && typeof jsonArr == 'object' && 'length' in jsonArr) {
        return this.emit('json_array', jsonArr);
      }
    }
  }
 
  // Emit as a raw line.
  return this.emit('stdout_line', line);
};
 
// Finish up any remaining data remaining in the line buffer, usually the
// last line of output if it does not have a terminating newline.
JSONSieve.prototype.close = function(cb) {
  if (this.lineBuffer_) {
    this.processLine_(this.lineBuffer_);
    delete this.lineBuffer_;
  }
  return (cb && cb());
};
 
 
module.exports = JSONSieve;