serialize-json
Version:
A serialize algorithm for JSON
191 lines (173 loc) • 5.01 kB
JavaScript
'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;