index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. var Transform = require('stream').Transform;
  2. var util = require('util');
  3. var clarinet = require('clarinet');
  4. /**
  5. * @constructor
  6. * @extends {Transform}
  7. */
  8. var JStream = module.exports = function(path) {
  9. Transform.call(this, { objectMode: true });
  10. var parser = this.parser = new clarinet.createStream();
  11. var self = this;
  12. var stack = [];
  13. var currObj = {};
  14. var currKey = 'root';
  15. var inArray;
  16. var pathMatch = true;
  17. var parentPathMatch = true;
  18. var comparator;
  19. if (path) {
  20. // Add some listeners only if path is given.
  21. var onvaluepath = function onvaluepath(value) {
  22. if (pathMatch && stack.length === path.length &&
  23. match(currKey, comparator)) {
  24. self.push(value);
  25. }
  26. };
  27. var onopenpath = function onopenpath() {
  28. if (stack.length) {
  29. parentPathMatch = pathMatch = parentPathMatch &&
  30. comparator !== undefined &&
  31. match(currKey, comparator);
  32. }
  33. comparator = path[stack.length];
  34. };
  35. parser.on('value', onvaluepath);
  36. parser.on('openobject', onopenpath);
  37. parser.on('openarray', onopenpath);
  38. }
  39. parser.on('value', function onvalue(value) {
  40. currObj[currKey] = value;
  41. if (inArray) {
  42. currKey++;
  43. }
  44. });
  45. parser.on('key', function onkey(key) {
  46. currKey = key;
  47. });
  48. function onopen(key) {
  49. var obj, openArray;
  50. if (key === undefined) {
  51. // openarray
  52. obj = currObj[currKey] = [];
  53. openArray = true;
  54. key = 0;
  55. } else {
  56. // openobject
  57. obj = currObj[currKey] = {};
  58. openArray = false;
  59. }
  60. stack.push({
  61. obj : currObj,
  62. key : currKey + (inArray ? 1 : ''),
  63. arr : inArray,
  64. path : pathMatch
  65. });
  66. currObj = obj;
  67. currKey = key;
  68. inArray = openArray;
  69. }
  70. function onclose() {
  71. var current = stack.pop();
  72. currObj = current.obj;
  73. currKey = current.key;
  74. inArray = current.arr;
  75. parentPathMatch = stack.length ? stack[stack.length - 1].path : true;
  76. }
  77. parser.on('openobject', onopen);
  78. parser.on('closeobject', onclose);
  79. parser.on('openarray', onopen);
  80. parser.on('closearray', onclose);
  81. parser.on('error', function onerror(err) {
  82. self.readable = false;
  83. self.writable = false;
  84. parser.emit = function() {};
  85. self.emit('error', err);
  86. });
  87. parser.on('end', self.push.bind(self, null));
  88. if (path) {
  89. var onclosepath = function onclosepath() {
  90. if (pathMatch && stack.length === path.length) {
  91. self.push(currObj[currKey]);
  92. }
  93. comparator = path[stack.length - 1];
  94. };
  95. parser.on('closeobject', onclosepath);
  96. parser.on('closearray', onclosepath);
  97. } else {
  98. // If `path` is not given, emit `data` event whenever a full
  99. // objectd on the root is parsed.
  100. parser.on('closeobject', function onobjectavailable() {
  101. if (!stack.length || stack.length === 1 && inArray) {
  102. var key = inArray ? currKey - 1 : currKey;
  103. self.push(currObj[key]);
  104. }
  105. });
  106. }
  107. };
  108. util.inherits(JStream, Transform);
  109. /**
  110. * Writes to the parser.
  111. *
  112. * @param {Buffer|String} chunk
  113. * @param {String} encoding
  114. * @param {Function(!Error)} callback
  115. */
  116. JStream.prototype._transform = function(chunk, encoding, callback) {
  117. this.parser.write(chunk);
  118. callback(null);
  119. };
  120. /**
  121. * Compare a key against a string, Number, RegExp, Boolean, or Function.
  122. *
  123. * @param {String} key
  124. * @param {String|Number|RegExp|Boolean|Function} comparator
  125. * @return {Boolean}
  126. */
  127. function match(key, comparator) {
  128. switch (typeof comparator) {
  129. case 'string':
  130. case 'number':
  131. return key === comparator;
  132. case 'boolean':
  133. return comparator;
  134. case 'function':
  135. return comparator(key);
  136. case 'object':
  137. if (comparator instanceof RegExp) {
  138. return comparator.test(key);
  139. }
  140. break;
  141. }
  142. throw new TypeError('Path object not supported.');
  143. }