123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- 'use strict';
- const debug = require('debug')('serialize-json#JSONEncoder');
- const is = require('is-type-of');
- const utility = require('utility');
- const REG_STR_REPLACER = /[\+ \|\^\%]/g;
- const ENCODER_REPLACER = {
- ' ': '+',
- '+': '%2B',
- '|': '%7C',
- '^': '%5E',
- '%': '%25',
- };
- const TOKEN_TRUE = -1;
- const TOKEN_FALSE = -2;
- const TOKEN_NULL = -3;
- const TOKEN_EMPTY_STRING = -4;
- const TOKEN_UNDEFINED = -5;
- class JSONEncoder {
- constructor() {
- this.dictionary = null;
- }
- encode(json) {
- this.dictionary = {
- strings: [],
- integers: [],
- floats: [],
- dates: [],
- };
- const ast = this._buildAst(json);
- let packed = this.dictionary.strings.join('|');
- packed += `^${this.dictionary.integers.join('|')}`;
- packed += `^${this.dictionary.floats.join('|')}`;
- packed += `^${this.dictionary.dates.join('|')}`;
- packed += `^${this._pack(ast)}`;
- debug('pack the json => %s', packed);
- return Buffer.from(packed);
- }
- _pack(ast) {
- if (is.array(ast)) {
- let packed = ast.shift();
- for (const item of ast) {
- packed += `${this._pack(item)}|`;
- }
- return (packed[packed.length - 1] === '|' ? packed.slice(0, -1) : packed) + ']';
- }
- const type = ast.type;
- const index = ast.index;
- const dictionary = this.dictionary;
- const strLen = dictionary.strings.length;
- const intLen = dictionary.integers.length;
- const floatLen = dictionary.floats.length;
- switch (type) {
- case 'string':
- return this._base10To36(index);
- case 'integer':
- return this._base10To36(strLen + index);
- case 'float':
- return this._base10To36(strLen + intLen + index);
- case 'date':
- return this._base10To36(strLen + intLen + floatLen + index);
- default:
- return this._base10To36(index);
- }
- }
- _encodeString(str) {
- return str.replace(REG_STR_REPLACER, a => ENCODER_REPLACER[a]);
- }
- _base10To36(num) {
- return num.toString(36).toUpperCase();
- }
- _dateTo36(date) {
- return this._base10To36(date.getTime());
- }
- _buildStringAst(str) {
- const dictionary = this.dictionary;
- if (str === '') {
- return {
- type: 'empty',
- index: TOKEN_EMPTY_STRING,
- };
- }
- const data = this._encodeString(str);
- return {
- type: 'string',
- index: dictionary.strings.push(data) - 1,
- };
- }
- _buildNumberAst(num) {
- const dictionary = this.dictionary;
- // integer
- if (num % 1 === 0) {
- const data = this._base10To36(num);
- return {
- type: 'integer',
- index: dictionary.integers.push(data) - 1,
- };
- }
- // float
- return {
- type: 'float',
- index: dictionary.floats.push(num) - 1,
- };
- }
- _buildObjectAst(obj) {
- if (obj === null) {
- return {
- type: 'null',
- index: TOKEN_NULL,
- };
- }
- if (is.date(obj)) {
- const dictionary = this.dictionary;
- const data = this._dateTo36(obj);
- return {
- type: 'date',
- index: dictionary.dates.push(data) - 1,
- };
- }
- let ast;
- if (is.array(obj)) {
- ast = [ '@' ];
- for (const item of obj) {
- ast.push(this._buildAst(item));
- }
- return ast;
- }
- if (is.buffer(obj)) {
- ast = [ '*' ];
- for (const item of obj.values()) {
- ast.push(this._buildAst(item));
- }
- return ast;
- }
- if (is.error(obj)) {
- ast = [ '#' ];
- ast.push(this._buildAst('message'));
- ast.push(this._buildAst(obj.message));
- ast.push(this._buildAst('stack'));
- ast.push(this._buildAst(obj.stack));
- } else {
- ast = [ '$' ];
- }
- for (const key in obj) {
- // support object without prototype, like: Object.create(null)
- if (!utility.has(obj, key)) {
- continue;
- }
- ast.push(this._buildAst(key));
- ast.push(this._buildAst(obj[key]));
- }
- return ast;
- }
- _buildAst(item) {
- const type = typeof item;
- debug('calling buildAst with type: %s and data: %j', type, item);
- switch (type) {
- case 'string':
- return this._buildStringAst(item);
- case 'number':
- return this._buildNumberAst(item);
- case 'boolean':
- return {
- type: 'boolean',
- index: item ? TOKEN_TRUE : TOKEN_FALSE,
- };
- case 'undefined':
- return {
- type: 'undefined',
- index: TOKEN_UNDEFINED,
- };
- case 'object':
- return this._buildObjectAst(item);
- default:
- debug('unsupported type: %s, return null', type);
- return {
- type: 'null',
- index: TOKEN_NULL,
- };
- }
- }
- }
- module.exports = JSONEncoder;
|