decoder.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. 'use strict';
  2. const debug = require('debug')('serialize-json#JSONDecoder');
  3. const TOKEN_TRUE = -1;
  4. const TOKEN_FALSE = -2;
  5. const TOKEN_NULL = -3;
  6. const TOKEN_EMPTY_STRING = -4;
  7. const TOKEN_UNDEFINED = -5;
  8. const REG_STR_REPLACER = /\+|%2B|%7C|%5E|%25/g;
  9. const DECODER_REPLACER = {
  10. '+': ' ',
  11. '%2B': '+',
  12. '%7C': '|',
  13. '%5E': '^',
  14. '%25': '%',
  15. };
  16. const TOKEN_SET = new Set([ '|', '$', '@', '*', '#', ']' ]);
  17. class JSONDecoder {
  18. constructor() {
  19. this.dictionary = [];
  20. this.tokens = [];
  21. this.tokensIndex = 0;
  22. }
  23. _decodeString(str) {
  24. // avoid Parent in (sliced string)
  25. // https://github.com/nodejs/help/issues/711
  26. // https://stackoverflow.com/questions/31712808/how-to-force-javascript-to-deep-copy-a-string
  27. const r = str.replace(REG_STR_REPLACER, a => DECODER_REPLACER[a]);
  28. return (' ' + r).slice(1);
  29. }
  30. _decodeDate(str) {
  31. return new Date(this._base36To10(str));
  32. }
  33. _base36To10(num) {
  34. return parseInt(num, 36);
  35. }
  36. _unpack() {
  37. const token = this.tokens[this.tokensIndex];
  38. switch (token) {
  39. case '@': // array
  40. {
  41. debug('--> unpack array begin');
  42. const arr = [];
  43. const tokensLen = this.tokens.length;
  44. for (this.tokensIndex++; this.tokensIndex < tokensLen; this.tokensIndex++) {
  45. const token = this.tokens[this.tokensIndex];
  46. if (token === ']') {
  47. debug('--> unpack array end, %j', arr);
  48. return arr;
  49. }
  50. arr.push(this._unpack());
  51. }
  52. return arr;
  53. }
  54. case '$': // object
  55. {
  56. debug('--> unpack plain object begin');
  57. const obj = {};
  58. const tokensLen = this.tokens.length;
  59. for (this.tokensIndex++; this.tokensIndex < tokensLen; this.tokensIndex++) {
  60. const token = this.tokens[this.tokensIndex];
  61. if (token === ']') {
  62. debug('--> unpack plain object end, %j', obj);
  63. return obj;
  64. }
  65. const key = this._unpack();
  66. this.tokensIndex++;
  67. obj[key] = this._unpack();
  68. }
  69. return obj;
  70. }
  71. case '*': // buffer
  72. {
  73. debug('--> unpack buffer begin');
  74. const arr = [];
  75. const tokensLen = this.tokens.length;
  76. for (this.tokensIndex++; this.tokensIndex < tokensLen; this.tokensIndex++) {
  77. const token = this.tokens[this.tokensIndex];
  78. if (token === ']') {
  79. debug('--> unpack buffer end, %j', arr);
  80. return Buffer.from(arr);
  81. }
  82. arr.push(this._unpack());
  83. }
  84. return Buffer.from(arr);
  85. }
  86. case '#': // error
  87. {
  88. debug('--> unpack error begin');
  89. const obj = {};
  90. const tokensLen = this.tokens.length;
  91. for (this.tokensIndex++; this.tokensIndex < tokensLen; this.tokensIndex++) {
  92. const token = this.tokens[this.tokensIndex];
  93. if (token === ']') {
  94. const err = new Error(obj.message);
  95. Object.assign(err, obj);
  96. debug('--> unpack error end, %j', err);
  97. return err;
  98. }
  99. const key = this._unpack();
  100. this.tokensIndex++;
  101. obj[key] = this._unpack();
  102. }
  103. const err = new Error(obj.message);
  104. Object.assign(err, obj);
  105. return err;
  106. }
  107. case TOKEN_TRUE:
  108. return true;
  109. case TOKEN_FALSE:
  110. return false;
  111. case TOKEN_NULL:
  112. return null;
  113. case TOKEN_EMPTY_STRING:
  114. return '';
  115. case TOKEN_UNDEFINED:
  116. return undefined;
  117. default:
  118. return this.dictionary[token];
  119. }
  120. }
  121. decode(buf) {
  122. this.dictionary = [];
  123. this.tokens = [];
  124. this.tokensIndex = 0;
  125. const packed = buf.toString();
  126. const arr = packed.split('^');
  127. if (arr[0]) {
  128. const strArr = arr[0].split('|');
  129. for (const str of strArr) {
  130. this.dictionary.push(this._decodeString(str));
  131. }
  132. }
  133. if (arr[1]) {
  134. const intArr = arr[1].split('|');
  135. for (const int of intArr) {
  136. this.dictionary.push(this._base36To10(int));
  137. }
  138. }
  139. if (arr[2]) {
  140. const floatArr = arr[2].split('|');
  141. for (const float of floatArr) {
  142. this.dictionary.push(parseFloat(float));
  143. }
  144. }
  145. if (arr[3]) {
  146. const dateArr = arr[3].split('|');
  147. for (const date of dateArr) {
  148. this.dictionary.push(this._decodeDate(date));
  149. }
  150. }
  151. debug('decode packed json => %s, with dictionary %j', packed, this.dictionary);
  152. let tmp = '';
  153. for (let i = 0, len = arr[4].length; i < len; ++i) {
  154. const symbol = arr[4][i];
  155. if (TOKEN_SET.has(symbol)) {
  156. if (tmp) {
  157. this.tokens.push(this._base36To10(tmp));
  158. tmp = '';
  159. }
  160. if (symbol !== '|') {
  161. this.tokens.push(symbol);
  162. }
  163. } else {
  164. tmp += symbol;
  165. }
  166. }
  167. if (tmp) {
  168. this.tokens.push(this._base36To10(tmp));
  169. }
  170. return this._unpack();
  171. }
  172. }
  173. module.exports = JSONDecoder;