'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;
|