joyson2
Version:
JOYFUL JSON | A smarter approach to serialize more than JSON, faster than ever!
330 lines (294 loc) • 13 kB
JavaScript
"use strict";
import { B64chromium } from "chromium-base64";
import DataProcessEngine from "./engine";
class JoyfulSerial {
constructor() {
"use strict";
// Initialize an empty array to store serialized data and a byte offset.
this._packBufferTemp8 = new Uint8Array(15000);
this._headerByteLength = 0;
this._offset = 0;
// Private properties holding various typed array types and their constructors.
this._b64 = new B64chromium();
this._b64Base64ToBytes = this._b64.base64ToBytes.bind(this._b64);
this._b64BytesToBase64 = this._b64.bytesToBase64.bind(this._b64);
this._textDecoder = new TextDecoder();
this._textDecoderFunction = this._textDecoder.decode.bind(this._textDecoder);
this._textEncoder = new TextEncoder();
this._textEncoderFunction = this._textEncoder.encode.bind(this._textEncoder);
this._textEncoderIntoFunction = this._textEncoder.encodeInto.bind(this._textEncoder);
this.encodeOtherBound = this.encodeOther.bind(this);
this.decodeOtherBound = this.decodeOther.bind(this);
this.stringifyBound = this._innerStringify.bind(this);
this.parseBound = this._innerParse.bind(this);
this.use_compressor = true;
this._setEngine();
}
_setEngine(){
this.engine = new DataProcessEngine(
undefined,
undefined,
undefined,
undefined,
this.encodeOtherBound,
this.decodeOtherBound,
this.stringifyBound,
this.parseBound,
this.use_compressor
);
}
get compress() {
return this.use_compressor;
}
set compress(v) {
this.use_compressor = Boolean(v);
this._setEngine();
}
_getComparator(){
"use strict";
return function (f) {
"use strict";
return function (node) {
"use strict";
return function (a, b) {
"use strict";
var aobj = {key: a, value: node[a]};
var bobj = {key: b, value: node[b]};
return f(aobj, bobj);
};
};
};
}
// Private method to serialize an object, iterating over its properties.
_stringifyObject(data) {
"use strict";
if(Array.isArray(data)){
var str = "[", i = 0, c = 0, l = data.length, keys = Object.keys(data), l2 = keys.length, key;
//if(l2 === 0){return "\"data:joyson/array;"+l+"\"";}
var pieces = new Array(l);
for (; (i|0) < (l|0); i = (i+1|0)>>>0) {
pieces[i] = this.engine.encode(data[i], false);
c++;
}
/*for (i = 0; (i|0) < (l2|0); i = (i+1|0)>>>0) {
key = keys[i];
if(parseInt(key) > 0 && parseInt(key) < l){
}else {
if(c > 0){ str += ","; }
str += '"'+key+'":' + this.engine.encode(data[key], false);
c++
}
}*/
return str+pieces.join(",")+"]";
}else {
var cmp = this._getComparator(), keys = Object.keys(data).sort(cmp && cmp(data)), key = '', i = 0, l = keys.length|0, out = '', newData, parseKey;
for (; (i|0) < (l|0); i = (i+1|0)>>>0) {
key = keys[i]+"";
newData = this.engine.encode(data[key], false);
if (!newData) { continue; }
if ((out.length|0) > 0){ out = out+','; }
parseKey = (key.length === 0) || key.includes("\x00") || key.startsWith("$");
switch (parseKey|0){
case 0x0:
out += this.engine.encode(key, false)+':' + newData; break;
case 0x1:
out += '"$'+btoa("$"+key)+'":' + newData; break;
}
}
return '{' + out + '}';
}
}
encodeOther(data, isShortBase){
"use strict";
if(isShortBase){
return this._packObject(data)
}else {
return this._stringifyObject(data)
}
}
decodeOther(dataStr, isShortBase){
"use strict";
if(isShortBase){
return this._unpackObject(dataStr)
}else {
return this._parseObject(dataStr)
}
}
// Public method to serialize data into a JSON string.
_stringify(data, isInside) {
"use strict";
var stringified = {};
stringified.header = this.engine.encode(data, false);
if(!isInside){
var buffers = this.engine.getAllArrayBuffer();
stringified.buffers = {};
for(var key in buffers) {
stringified.buffers[key] = this._b64BytesToBase64(new Uint8Array(buffers[key]));
}
}
return JSON.stringify(stringified);
}
stringify(data){ "use strict"; return this._stringify(data, false)}
_innerStringify(data){ "use strict"; return this._stringify(data, true)}
// Private method to parse an object, iterating over its properties.
_parseObject(obj) {
"use strict";
if(Array.isArray(obj)){
var decodedArray = [];
for (var i = 0, l = obj.length; (i|0) < (l|0); i = (i+1|0)>>>0) {
decodedArray.push(this.engine.decode(obj[i]));
}
return decodedArray;
}else {
var decodedObj = {}, keys = Object.keys(obj), key = "", keyMustDecode, newData, i = 0, l = keys.length|0;
for (; (i | 0) < (l | 0); i = (i + 1 | 0) >>> 0) {
key = keys[i];
keyMustDecode = this._keyMustDecode(key);
newData = this.engine.decode(obj[key]);
switch (keyMustDecode|0){
case 0x00:
decodedObj[key] = newData; break;
case 0x1:
decodedObj[atob(key.slice(1)).slice(1)] = newData; break;
}
}
return decodedObj;
}
}
// Public method to parse a JSON string into the original data format.
_parse(data, isInside) {
"use strict";
var parsed = JSON.parse(data);
if(!isInside) {
var b64Base64ToBytes = this._b64Base64ToBytes, buffers = {};
Object.entries(parsed.buffers).forEach(function (entry){
"use strict";
var key = entry[0], value = entry[1];
buffers[key] = b64Base64ToBytes(value).buffer;
});
this.engine.setAllArrayBuffer(buffers);
}
return this.engine.decode(JSON.parse(parsed.header));
}
parse(data){ "use strict"; return this._parse(data, false); }
_innerParse(data){ "use strict"; return this._parse(data, true); }
// Private method to pack an object, iterating over its properties.
_packObject(obj) {
"use strict";
if(Array.isArray(obj)){
var encodedArray = [], i = 0, l = obj.length;
for (; (i|0) < (l|0); i = (i+1|0)>>>0) {
encodedArray.push(this.engine.encode(obj[i], true));
}
return encodedArray;
}else {
var encodedObj = {}, keys = Object.keys(obj), key = "", i = 0, l = keys.length|0, newData, parseKey;
for (; (i|0) < (l|0); i = (i+1|0)>>>0) {
key = keys[i]+"";
newData = this.engine.encode(obj[key], true);
if (!newData) { continue; }
parseKey = (key === "") || key.includes("\x00") || key.startsWith("$");
switch (parseKey|0){
case 0x0:
encodedObj[this.engine.encode(key, true)] = newData; break;
case 0x1:
encodedObj["$"+btoa("$"+key)] = newData; break;
}
}
return encodedObj;
}
}
// Public method to pack data into a binary format for efficient storage/transmission.
pack(data) {
"use strict";
var headerString = JSON.stringify(this.engine.encode(data, true));
var buffers = this.engine.getAllArrayBuffer(), bufferByteLength = 0;
var bufferLength = Object.keys(buffers).length;
for(var key in buffers) {
bufferByteLength += buffers[key].byteLength;
}
var estimatedHeaderSize = headerString.length * 2 + 5;
var estimatedSize = estimatedHeaderSize + bufferByteLength + 4;
if(this._packBufferTemp8.length < estimatedSize) {
this._packBufferTemp8 = new Uint8Array(estimatedSize);
}
var encodeResults = this._textEncoderIntoFunction(headerString, this._packBufferTemp8.subarray(4, 4 + estimatedHeaderSize));
if(encodeResults.written > estimatedHeaderSize) { throw new Error("Header in json is too fat!")}
var headerByteLength = encodeResults.written;
this._packBufferTemp8[0] = headerByteLength >> 0 & 0xff;
this._packBufferTemp8[1] = headerByteLength >> 8 & 0xff;
this._packBufferTemp8[2] = headerByteLength >> 16 & 0xff;
this._packBufferTemp8[3] = headerByteLength >> 24 & 0xff;
// Combine header and body buffers into a single Uint8Array.
var bodyOffset = headerByteLength;
bufferByteLength = 4;
// Write how many buffer there are
this._packBufferTemp8[bodyOffset+bufferByteLength] = bufferLength >> 0 & 0xff;
this._packBufferTemp8[bodyOffset+bufferByteLength+1] = bufferLength >> 8 & 0xff;
this._packBufferTemp8[bodyOffset+bufferByteLength+2] = bufferLength >> 16 & 0xff;
this._packBufferTemp8[bodyOffset+bufferByteLength+3] = bufferLength >> 24 & 0xff;
bufferByteLength += 4;
for(var key in buffers) {
var buffer = buffers[key];
var length = buffer.byteLength;
this._packBufferTemp8[bodyOffset+bufferByteLength] = length >> 0 & 0xff;
this._packBufferTemp8[bodyOffset+bufferByteLength+1] = length >> 8 & 0xff;
this._packBufferTemp8[bodyOffset+bufferByteLength+2] = length >> 16 & 0xff;
this._packBufferTemp8[bodyOffset+bufferByteLength+3] = length >> 24 & 0xff;
bufferByteLength += 4;
this._packBufferTemp8.set(new Uint8Array(buffer), bodyOffset+bufferByteLength);
bufferByteLength += length;
}
return this._packBufferTemp8.slice(0, bodyOffset+bufferByteLength);
}
_keyMustDecode(key) {
"use strict";
return key.startsWith("$");
}
// Private method to unpack an object, iterating over its properties.
_unpackObject(obj) {
"use strict";
if(Array.isArray(obj)){
var decodedArray = [];
for (var i = 0, l = obj.length; (i|0) < (l|0); i = (i+1|0)>>>0) {
decodedArray.push(this.engine.decode(obj[i], true));
}
return decodedArray;
}else {
var decodedObj = {}, keys = Object.keys(obj), key = "", keyMustDecode, newData, i = 0, l = keys.length|0;
for (; (i | 0) < (l | 0); i = (i + 1 | 0) >>> 0) {
key = keys[i];
keyMustDecode = this._keyMustDecode(key);
newData = this.engine.decode(obj[key], true);
switch (keyMustDecode|0){
case 0x00:
decodedObj[key] = newData; break;
case 0x1:
decodedObj[atob(key.slice(1)).slice(1)] = newData; break;
}
}
return decodedObj;
}
}
// Public method to unpack data from its binary format back into the original data format.
unpack(packedData) {
"use strict";
var headerByteLength = packedData[0] << 0 | packedData[1] << 8 | packedData[2] << 16 | packedData[3] << 24;
var headerStart = 4;
var headerStop = 4 + headerByteLength;
var bodyStart = headerStop+4;
var header = JSON.parse(this._textDecoderFunction(packedData.subarray(headerStart, headerStop)));
var bufferCount = packedData[headerStop] << 0 | packedData[headerStop+1] << 8 | packedData[headerStop+2] << 16 | packedData[headerStop+3] << 24;
var buffers = {};
var currentOffset = bodyStart;
for(var i = 0; i < bufferCount; i++){
var length = packedData[currentOffset] << 0 | packedData[currentOffset+1] << 8 | packedData[currentOffset+2] << 16 | packedData[currentOffset+3] << 24;
currentOffset += 4;
buffers[i] = packedData.buffer.slice(currentOffset, currentOffset+length)
currentOffset += length;
}
this.engine.setAllArrayBuffer(buffers);
return this.engine.decode(header, true);
}
}
export default JoyfulSerial;