diff --git a/Parser.js b/Parser.js index a5995ae..1265cec 100644 --- a/Parser.js +++ b/Parser.js @@ -55,6 +55,7 @@ class Parser extends Utf8Stream { super(Object.assign({}, options, {readableObjectMode: true})); this._packKeys = this._packStrings = this._packNumbers = this._streamKeys = this._streamStrings = this._streamNumbers = true; + this.keepFormatter = false; if (options) { 'packValues' in options && (this._packKeys = this._packStrings = this._packNumbers = options.packValues); 'packKeys' in options && (this._packKeys = options.packKeys); @@ -64,6 +65,7 @@ class Parser extends Utf8Stream { 'streamKeys' in options && (this._streamKeys = options.streamKeys); 'streamStrings' in options && (this._streamStrings = options.streamStrings); 'streamNumbers' in options && (this._streamNumbers = options.streamNumbers); + 'keepFormatter' in options && (this.keepFormatter = options.keepFormatter); this._jsonStreaming = options.jsonStreaming; } !this._packKeys && (this._streamKeys = true); @@ -187,7 +189,9 @@ class Parser extends Utf8Stream { this.push({name: value + 'Value', value: values[value]}); this._expect = expected[this._parent]; break; - // default: // ws + default: + // ws + this.keepFormatter && this.push({name: 'formatter', value: value}); } if (noSticky) { this._buffer = this._buffer.slice(value.length); @@ -261,6 +265,8 @@ class Parser extends Utf8Stream { this.push({name: 'endObject'}); this._parent = this._stack.pop(); this._expect = expected[this._parent]; + } else { + this.keepFormatter && this.push({name: 'formatter', value}); } if (noSticky) { this._buffer = this._buffer.slice(value.length); @@ -277,6 +283,7 @@ class Parser extends Utf8Stream { } value = match[0]; value === ':' && (this._expect = 'value'); + this.keepFormatter && this.push({name: 'formatter', value}); if (noSticky) { this._buffer = this._buffer.slice(value.length); } else { @@ -302,6 +309,7 @@ class Parser extends Utf8Stream { value = match[0]; if (value === ',') { this._expect = this._expect === 'arrayStop' ? 'value' : 'key'; + this.keepFormatter && this.push({name: 'formatter', value}); } else if (value === '}' || value === ']') { if (value === '}' ? this._expect === 'arrayStop' : this._expect !== 'arrayStop') { return callback(new Error("Parser cannot parse input: expected '" + (this._expect === 'arrayStop' ? ']' : '}') + "'")); @@ -309,6 +317,8 @@ class Parser extends Utf8Stream { this.push({name: value === '}' ? 'endObject' : 'endArray'}); this._parent = this._stack.pop(); this._expect = expected[this._parent]; + } else { + this.keepFormatter && this.push({name: 'formatter', value}); } if (noSticky) { this._buffer = this._buffer.slice(value.length); @@ -532,6 +542,7 @@ class Parser extends Utf8Stream { } else { index += value.length; } + this.keepFormatter && this.push({name: 'formatter', value }); break; } } diff --git a/Stringer.js b/Stringer.js index df23c21..b5460e4 100644 --- a/Stringer.js +++ b/Stringer.js @@ -24,6 +24,7 @@ const noCommaAfter = {startObject: 1, startArray: 1, endKey: 1, keyValue: 1}, trueValue: 'true', falseValue: 'false' }; +const symbolsEndKeyForKeepFormatter = '"'; const skipValue = endName => function(chunk, encoding, callback) { @@ -47,11 +48,13 @@ class Stringer extends Transform { super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: false})); this._values = {}; + this.keepFormatter = false; if (options) { 'useValues' in options && (this._values.keyValue = this._values.stringValue = this._values.numberValue = options.useValues); 'useKeyValues' in options && (this._values.keyValue = options.useKeyValues); 'useStringValues' in options && (this._values.stringValue = options.useStringValues); 'useNumberValues' in options && (this._values.numberValue = options.useNumberValues); + 'keepFormatter' in options && (this.keepFormatter = options.keepFormatter); this._makeArray = options.makeArray; } @@ -81,10 +84,14 @@ class Stringer extends Transform { _transform(chunk, _, callback) { if (this._values[chunk.name]) { - if (this._depth && noCommaAfter[this._prev] !== 1) this.push(','); + if (this._depth && noCommaAfter[this._prev] !== 1 && !this.keepFormatter) this.push(','); switch (chunk.name) { case 'keyValue': - this.push('"' + sanitizeString(chunk.value) + '":'); + if (!(this.keepFormatter && chunk.name === 'endKey')) { + this.push('"' + sanitizeString(chunk.value) + symbols.endKey); + } else { + this.push('"' + sanitizeString(chunk.value) + symbolsEndKeyForKeepFormatter); + } break; case 'stringValue': this.push('"' + sanitizeString(chunk.value)+ '"'); @@ -92,16 +99,26 @@ class Stringer extends Transform { case 'numberValue': this.push(chunk.value); break; + case 'formatter': + this.keepFormatter && this.push(chunk.value); + break; } } else { // filter out values switch (chunk.name) { + case 'formatter': + this.keepFormatter && this.push(chunk.value); + break; case 'endObject': case 'endArray': case 'endKey': case 'endString': case 'endNumber': - this.push(symbols[chunk.name]); + if (!(this.keepFormatter && chunk.name === 'endKey')) { + this.push(symbols[chunk.name]); + } else { + this.push(symbolsEndKeyForKeepFormatter); + } break; case 'stringChunk': this.push(sanitizeString(chunk.value)); @@ -127,7 +144,7 @@ class Stringer extends Transform { // case 'startObject': case 'startArray': case 'startKey': case 'startString': // case 'startNumber': case 'nullValue': case 'trueValue': case 'falseValue': if (this._depth) { - if (noCommaAfter[this._prev] !== 1) this.push(','); + if (noCommaAfter[this._prev] !== 1 && !this.keepFormatter) this.push(','); } else { if (noSpaceAfter[this._prev] !== 1 && noSpaceBefore[chunk.name] !== 1) this.push(' '); } @@ -140,7 +157,9 @@ class Stringer extends Transform { --this._depth; } } - this._prev = chunk.name; + if (chunk.name !== 'formatter') { + this._prev = chunk.name; + } callback(null); } } diff --git a/tests/test_parser.js b/tests/test_parser.js index a51e6bb..d37a293 100644 --- a/tests/test_parser.js +++ b/tests/test_parser.js @@ -375,5 +375,33 @@ unit.add(module, [ eval(t.ASSERT('result.length === 0')); async.done(); }); + }, + function test_parser_keep_formatter(t) { + const async = t.startAsync('test_parser_keep_formatter'); + const input = '{ "a" : 1 } ', + pipeline = new ReadString(input).pipe(new Parser({keepFormatter: true})), + result = []; + + pipeline.on('data', chunk => result.push({name: chunk.name, val: chunk.value})); + pipeline.on('end', () => { + eval(t.ASSERT('result.length === 16')); + eval(t.TEST("result[0].name === 'startObject'")); + eval(t.TEST("result[1].name === 'formatter' && result[1].val === ' '")); + eval(t.TEST("result[2].name === 'startKey'")); + eval(t.TEST("result[3].name === 'stringChunk' && result[3].val === 'a'")); + eval(t.TEST("result[4].name === 'endKey'")); + eval(t.TEST("result[5].name === 'keyValue' && result[5].val === 'a'")); + eval(t.TEST("result[6].name === 'formatter' && result[6].val === ' '")); + eval(t.TEST("result[7].name === 'formatter' && result[7].val === ':'")); + eval(t.TEST("result[8].name === 'formatter' && result[8].val === ' '")); + eval(t.TEST("result[9].name === 'startNumber'")); + eval(t.TEST("result[10].name === 'numberChunk' && result[10].val === '1'")); + eval(t.TEST("result[11].name === 'endNumber'")); + eval(t.TEST("result[12].name === 'numberValue' && result[12].val === '1'")); + eval(t.TEST("result[13].name === 'formatter' && result[13].val === ' '")); + eval(t.TEST("result[14].name === 'endObject'")); + eval(t.TEST("result[15].name === 'formatter' && result[15].val === ' '")); + async.done(); + }); } ]); diff --git a/tests/test_stringer.js b/tests/test_stringer.js index 9f3539e..37dd032 100644 --- a/tests/test_stringer.js +++ b/tests/test_stringer.js @@ -222,5 +222,35 @@ unit.add(module, [ }); new ReadString(string).pipe(parser); + }, + function test_stringer_keep_formatter(t) { + const async = t.startAsync('test_stringer_strings_with_special_symbols'); + + const parser = makeParser({keepFormatter: true}), + stringer = new Stringer({keepFormatter: true}), + jsonStr = ` +{ + "a": 1, + "b": 2 , + "b" : "3" , + "d": [1, "2" , [3, "4"] ,{ "a": 1 , "b":2 }], + "e": { + "f": 6, + "g" : "7", + "h": { } + } +} + `; + let buffer = ''; + + parser.pipe(stringer); + + stringer.on('data', data => (buffer += data)); + stringer.on('end', () => { + eval(t.TEST('jsonStr === buffer')); + async.done(); + }); + + new ReadString(jsonStr).pipe(parser); } ]);