index.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. 'use strict';
  2. const through2 = require('through2');
  3. const Parser = require('jsonparse');
  4. /**
  5. * Check whether a x and y are equal, or x matches y, or x(y) is truthy.
  6. * @param {boolean | string | RegExp | (args: any[]) => boolean} x
  7. * @param {*} y
  8. * @returns {boolean}
  9. */
  10. const check = (x, y) => {
  11. if (typeof x === 'string') {
  12. return y === x;
  13. }
  14. if (x && typeof x.exec === 'function') {
  15. return x.exec(y);
  16. }
  17. if (typeof x === 'boolean' || typeof x === 'object') {
  18. return x;
  19. }
  20. if (typeof x === 'function') {
  21. return x(y);
  22. }
  23. return false;
  24. };
  25. module.exports.parse = function(path, map) {
  26. let header, footer;
  27. const parser = new Parser();
  28. const stream = through2.obj(
  29. (chunk, enc, cb) => {
  30. if (typeof chunk === 'string') {
  31. chunk = Buffer.from(chunk);
  32. }
  33. parser.write(chunk);
  34. cb();
  35. },
  36. cb => {
  37. if (header) {
  38. stream.emit('header', header);
  39. }
  40. if (footer) {
  41. stream.emit('footer', footer);
  42. }
  43. if (parser.tState !== Parser.C.START || parser.stack.length > 0) {
  44. cb(new Error('Incomplete JSON'));
  45. return;
  46. }
  47. cb();
  48. }
  49. );
  50. if (typeof path === 'string') {
  51. path = path.split('.').map(e => {
  52. if (e === '$*') {
  53. return { emitKey: true };
  54. }
  55. if (e === '*') {
  56. return true;
  57. }
  58. if (e === '') {
  59. // '..'.split('.') returns an empty string
  60. return { recurse: true };
  61. }
  62. return e;
  63. });
  64. }
  65. if (!path || !path.length) {
  66. path = null;
  67. }
  68. parser.onValue = function(value) {
  69. if (!this.root) { stream.root = value; }
  70. if (!path) return;
  71. let i = 0; // iterates on path
  72. let j = 0; // iterates on stack
  73. let emitKey = false;
  74. let emitPath = false;
  75. while (i < path.length) {
  76. const key = path[i];
  77. let c;
  78. j++;
  79. if (key && !key.recurse) {
  80. c = j === this.stack.length ? this : this.stack[j];
  81. if (!c) return;
  82. if (!check(key, c.key)) {
  83. setHeaderFooter(c.key, value);
  84. return;
  85. }
  86. emitKey = !!key.emitKey;
  87. emitPath = !!key.emitPath;
  88. i++;
  89. } else {
  90. i++;
  91. const nextKey = path[i];
  92. if (!nextKey) return;
  93. // eslint-disable-next-line no-constant-condition
  94. while (true) {
  95. c = j === this.stack.length ? this : this.stack[j];
  96. if (!c) return;
  97. if (check(nextKey, c.key)) {
  98. i++;
  99. if (!Object.isFrozen(this.stack[j])) {
  100. this.stack[j].value = null;
  101. }
  102. break;
  103. } else {
  104. setHeaderFooter(c.key, value);
  105. }
  106. j++;
  107. }
  108. }
  109. }
  110. // emit header
  111. if (header) {
  112. stream.emit('header', header);
  113. header = false;
  114. }
  115. if (j !== this.stack.length) return;
  116. const actualPath = this.stack.slice(1).map(element => element.key);
  117. actualPath.push(this.key);
  118. let data = this.value[this.key];
  119. if (data != null) {
  120. if ((data = map ? map(data, actualPath) : data) != null) {
  121. if (emitKey || emitPath) {
  122. data = {
  123. value: data
  124. };
  125. if (emitKey) {
  126. data.key = this.key;
  127. }
  128. if (emitPath) {
  129. data.path = actualPath;
  130. }
  131. }
  132. stream.push(data);
  133. }
  134. }
  135. delete this.value[this.key];
  136. for (const k in this.stack) {
  137. if (!Object.isFrozen(this.stack[k])) {
  138. this.stack[k].value = null;
  139. }
  140. }
  141. };
  142. parser._onToken = parser.onToken;
  143. parser.onToken = function(token, value) {
  144. parser._onToken(token, value);
  145. if (this.stack.length === 0) {
  146. if (stream.root) {
  147. if (!path) { stream.push(stream.root); }
  148. stream.root = null;
  149. }
  150. }
  151. };
  152. parser.onError = function(err) {
  153. if (err.message.includes('at position')) {
  154. err.message = 'Invalid JSON (' + err.message + ')';
  155. }
  156. stream.destroy(err);
  157. };
  158. return stream;
  159. function setHeaderFooter(key, value) {
  160. // header has not been emitted yet
  161. if (header !== false) {
  162. header = header || {};
  163. header[key] = value;
  164. }
  165. // footer has not been emitted yet but header has
  166. if (footer !== false && header === false) {
  167. footer = footer || {};
  168. footer[key] = value;
  169. }
  170. }
  171. };