123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- 'use strict';
- const through2 = require('through2');
- const Parser = require('jsonparse');
- /**
- * Check whether a x and y are equal, or x matches y, or x(y) is truthy.
- * @param {boolean | string | RegExp | (args: any[]) => boolean} x
- * @param {*} y
- * @returns {boolean}
- */
- const check = (x, y) => {
- if (typeof x === 'string') {
- return y === x;
- }
- if (x && typeof x.exec === 'function') {
- return x.exec(y);
- }
- if (typeof x === 'boolean' || typeof x === 'object') {
- return x;
- }
- if (typeof x === 'function') {
- return x(y);
- }
- return false;
- };
- module.exports.parse = function(path, map) {
- let header, footer;
- const parser = new Parser();
- const stream = through2.obj(
- (chunk, enc, cb) => {
- if (typeof chunk === 'string') {
- chunk = Buffer.from(chunk);
- }
- parser.write(chunk);
- cb();
- },
- cb => {
- if (header) {
- stream.emit('header', header);
- }
- if (footer) {
- stream.emit('footer', footer);
- }
- if (parser.tState !== Parser.C.START || parser.stack.length > 0) {
- cb(new Error('Incomplete JSON'));
- return;
- }
- cb();
- }
- );
- if (typeof path === 'string') {
- path = path.split('.').map(e => {
- if (e === '$*') {
- return { emitKey: true };
- }
- if (e === '*') {
- return true;
- }
- if (e === '') {
- // '..'.split('.') returns an empty string
- return { recurse: true };
- }
- return e;
- });
- }
- if (!path || !path.length) {
- path = null;
- }
- parser.onValue = function(value) {
- if (!this.root) { stream.root = value; }
- if (!path) return;
- let i = 0; // iterates on path
- let j = 0; // iterates on stack
- let emitKey = false;
- let emitPath = false;
- while (i < path.length) {
- const key = path[i];
- let c;
- j++;
- if (key && !key.recurse) {
- c = j === this.stack.length ? this : this.stack[j];
- if (!c) return;
- if (!check(key, c.key)) {
- setHeaderFooter(c.key, value);
- return;
- }
- emitKey = !!key.emitKey;
- emitPath = !!key.emitPath;
- i++;
- } else {
- i++;
- const nextKey = path[i];
- if (!nextKey) return;
- // eslint-disable-next-line no-constant-condition
- while (true) {
- c = j === this.stack.length ? this : this.stack[j];
- if (!c) return;
- if (check(nextKey, c.key)) {
- i++;
- if (!Object.isFrozen(this.stack[j])) {
- this.stack[j].value = null;
- }
- break;
- } else {
- setHeaderFooter(c.key, value);
- }
- j++;
- }
- }
- }
- // emit header
- if (header) {
- stream.emit('header', header);
- header = false;
- }
- if (j !== this.stack.length) return;
- const actualPath = this.stack.slice(1).map(element => element.key);
- actualPath.push(this.key);
- let data = this.value[this.key];
- if (data != null) {
- if ((data = map ? map(data, actualPath) : data) != null) {
- if (emitKey || emitPath) {
- data = {
- value: data
- };
- if (emitKey) {
- data.key = this.key;
- }
- if (emitPath) {
- data.path = actualPath;
- }
- }
- stream.push(data);
- }
- }
- delete this.value[this.key];
- for (const k in this.stack) {
- if (!Object.isFrozen(this.stack[k])) {
- this.stack[k].value = null;
- }
- }
- };
- parser._onToken = parser.onToken;
- parser.onToken = function(token, value) {
- parser._onToken(token, value);
- if (this.stack.length === 0) {
- if (stream.root) {
- if (!path) { stream.push(stream.root); }
- stream.root = null;
- }
- }
- };
- parser.onError = function(err) {
- if (err.message.includes('at position')) {
- err.message = 'Invalid JSON (' + err.message + ')';
- }
- stream.destroy(err);
- };
- return stream;
- function setHeaderFooter(key, value) {
- // header has not been emitted yet
- if (header !== false) {
- header = header || {};
- header[key] = value;
- }
- // footer has not been emitted yet but header has
- if (footer !== false && header === false) {
- footer = footer || {};
- footer[key] = value;
- }
- }
- };
|