123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- 'use strict';
- const debug = require('debug')('serialize-json#JSONDecoder');
- const TOKEN_TRUE = -1;
- const TOKEN_FALSE = -2;
- const TOKEN_NULL = -3;
- const TOKEN_EMPTY_STRING = -4;
- const TOKEN_UNDEFINED = -5;
- const REG_STR_REPLACER = /\+|%2B|%7C|%5E|%25/g;
- const DECODER_REPLACER = {
- '+': ' ',
- '%2B': '+',
- '%7C': '|',
- '%5E': '^',
- '%25': '%',
- };
- const TOKEN_SET = new Set([ '|', '$', '@', '*', '#', ']' ]);
- class JSONDecoder {
- constructor() {
- this.dictionary = [];
- this.tokens = [];
- this.tokensIndex = 0;
- }
- _decodeString(str) {
- // avoid Parent in (sliced string)
- // https://github.com/nodejs/help/issues/711
- // https://stackoverflow.com/questions/31712808/how-to-force-javascript-to-deep-copy-a-string
- const r = str.replace(REG_STR_REPLACER, a => DECODER_REPLACER[a]);
- return (' ' + r).slice(1);
- }
- _decodeDate(str) {
- return new Date(this._base36To10(str));
- }
- _base36To10(num) {
- return parseInt(num, 36);
- }
- _unpack() {
- const token = this.tokens[this.tokensIndex];
- switch (token) {
- case '@': // array
- {
- debug('--> unpack array begin');
- const arr = [];
- const tokensLen = this.tokens.length;
- for (this.tokensIndex++; this.tokensIndex < tokensLen; this.tokensIndex++) {
- const token = this.tokens[this.tokensIndex];
- if (token === ']') {
- debug('--> unpack array end, %j', arr);
- return arr;
- }
- arr.push(this._unpack());
- }
- return arr;
- }
- case '$': // object
- {
- debug('--> unpack plain object begin');
- const obj = {};
- const tokensLen = this.tokens.length;
- for (this.tokensIndex++; this.tokensIndex < tokensLen; this.tokensIndex++) {
- const token = this.tokens[this.tokensIndex];
- if (token === ']') {
- debug('--> unpack plain object end, %j', obj);
- return obj;
- }
- const key = this._unpack();
- this.tokensIndex++;
- obj[key] = this._unpack();
- }
- return obj;
- }
- case '*': // buffer
- {
- debug('--> unpack buffer begin');
- const arr = [];
- const tokensLen = this.tokens.length;
- for (this.tokensIndex++; this.tokensIndex < tokensLen; this.tokensIndex++) {
- const token = this.tokens[this.tokensIndex];
- if (token === ']') {
- debug('--> unpack buffer end, %j', arr);
- return Buffer.from(arr);
- }
- arr.push(this._unpack());
- }
- return Buffer.from(arr);
- }
- case '#': // error
- {
- debug('--> unpack error begin');
- const obj = {};
- const tokensLen = this.tokens.length;
- for (this.tokensIndex++; this.tokensIndex < tokensLen; this.tokensIndex++) {
- const token = this.tokens[this.tokensIndex];
- if (token === ']') {
- const err = new Error(obj.message);
- Object.assign(err, obj);
- debug('--> unpack error end, %j', err);
- return err;
- }
- const key = this._unpack();
- this.tokensIndex++;
- obj[key] = this._unpack();
- }
- const err = new Error(obj.message);
- Object.assign(err, obj);
- return err;
- }
- case TOKEN_TRUE:
- return true;
- case TOKEN_FALSE:
- return false;
- case TOKEN_NULL:
- return null;
- case TOKEN_EMPTY_STRING:
- return '';
- case TOKEN_UNDEFINED:
- return undefined;
- default:
- return this.dictionary[token];
- }
- }
- decode(buf) {
- this.dictionary = [];
- this.tokens = [];
- this.tokensIndex = 0;
- const packed = buf.toString();
- const arr = packed.split('^');
- if (arr[0]) {
- const strArr = arr[0].split('|');
- for (const str of strArr) {
- this.dictionary.push(this._decodeString(str));
- }
- }
- if (arr[1]) {
- const intArr = arr[1].split('|');
- for (const int of intArr) {
- this.dictionary.push(this._base36To10(int));
- }
- }
- if (arr[2]) {
- const floatArr = arr[2].split('|');
- for (const float of floatArr) {
- this.dictionary.push(parseFloat(float));
- }
- }
- if (arr[3]) {
- const dateArr = arr[3].split('|');
- for (const date of dateArr) {
- this.dictionary.push(this._decodeDate(date));
- }
- }
- debug('decode packed json => %s, with dictionary %j', packed, this.dictionary);
- let tmp = '';
- for (let i = 0, len = arr[4].length; i < len; ++i) {
- const symbol = arr[4][i];
- if (TOKEN_SET.has(symbol)) {
- if (tmp) {
- this.tokens.push(this._base36To10(tmp));
- tmp = '';
- }
- if (symbol !== '|') {
- this.tokens.push(symbol);
- }
- } else {
- tmp += symbol;
- }
- }
- if (tmp) {
- this.tokens.push(this._base36To10(tmp));
- }
- return this._unpack();
- }
- }
- module.exports = JSONDecoder;
|