@cloudpss/ubjson
Version:
Opinionated UBJSON encoder/decoder for CloudPSS.
181 lines • 7.52 kB
JavaScript
import { I8_MASK, writeNumber, writeTypedArray, writeTypedArrayData, writeTypedArrayHeader, } from '../helper/encode.js';
import { unsupportedType } from '../helper/errors.js';
import { stringByteLength, encodeInto } from '../helper/string-encoder.js';
const LARGE_DATA_LENGTH = 65536;
const { isArray } = Array;
// eslint-disable-next-line @typescript-eslint/unbound-method
const { isView } = ArrayBuffer;
const { keys: objectKeys } = Object;
/** 编码至 ubjson */
export class EncoderBase {
/** 序列化对象时排序属性 */
sortObjectKeys = false;
/** 当前写指针位置 */
length = 0;
/** 数据 */
data;
/** buffer 的 DataView */
view;
/** 编码至 ubjson,对于 `undefined` 写入 NOOP */
writeValue(value) {
if (value === undefined) {
this.ensureCapacity(1);
this.data[this.length++] = 78 /* constants.NO_OP */;
return;
}
this.write(value);
}
/** 写入一个对象 */
write(value) {
switch (typeof value) {
case 'string':
if (value.length === 1 && value.charCodeAt(0) < 0x80) {
// 1 byte ascii char
this.ensureCapacity(2);
this.data[this.length++] = 67 /* constants.CHAR */;
this.data[this.length++] = value.charCodeAt(0);
}
else {
this.ensureCapacity(2);
this.data[this.length++] = 83 /* constants.STRING */;
this.writeStringData(value);
}
return;
case 'number':
return writeNumber(this, value);
case 'object': {
if (value === null) {
this.ensureCapacity(1);
this.data[this.length++] = 90 /* constants.NULL */;
return;
}
if (isArray(value)) {
this.ensureCapacity(1);
this.data[this.length++] = 91 /* constants.ARRAY */;
const size = value.length;
for (let index = 0; index < size; index++) {
const element = value[index];
// 在数组中 undefined 和 function 也被视作 null 进行序列化
if (element == null || typeof element == 'function') {
this.ensureCapacity(1);
this.data[this.length++] = 90 /* constants.NULL */;
}
else {
this.write(element);
}
}
this.ensureCapacity(1);
this.data[this.length++] = 93 /* constants.ARRAY_END */;
return;
}
if (isView(value)) {
if (value.byteLength <= LARGE_DATA_LENGTH) {
writeTypedArray(this, value);
return;
}
const type = writeTypedArrayHeader(this, value);
this.writeLargeTypedArrayData(type, value);
return;
}
const { toJSON } = value;
if (typeof toJSON == 'function') {
this.write(toJSON.call(value));
return;
}
// 生成稳定的结果以便 hash 计算
const keys = objectKeys(value);
const size = keys.length;
if (this.sortObjectKeys && size > 1) {
keys.sort();
}
this.ensureCapacity(2 + size);
this.data[this.length++] = 123 /* constants.OBJECT */;
for (let index = 0; index < size; index++) {
const key = keys[index];
const element = value[key];
if (element === undefined || typeof element == 'function')
continue;
this.writeStringData(key);
this.write(element);
}
this.ensureCapacity(1);
this.data[this.length++] = 125 /* constants.OBJECT_END */;
return;
}
case 'boolean':
this.ensureCapacity(1);
this.data[this.length++] = value ? 84 /* constants.TRUE */ : 70 /* constants.FALSE */;
return;
case 'bigint':
// int32 range
if (value >= -2147483648n && value <= 2147483647n) {
this.write(Number(value));
}
// int64 range
else if (value >= -9223372036854775808n && value <= 9223372036854775807n) {
this.ensureCapacity(9);
this.data[this.length++] = 76 /* constants.INT64 */;
this.view.setBigInt64(this.length, value);
this.length += 8;
}
else {
throw new RangeError(`BigInt value out of range: ${value}`);
}
return;
default:
unsupportedType(value);
}
}
/** writeStringData */
writeStringData(value) {
const strLength = value.length;
if (strLength > LARGE_DATA_LENGTH) {
return this.writeLargeStringData(value);
}
// 对于短字符串,直接计算最大使用空间
const maxUsage = strLength * 3;
// 一次性分配 setLength 和 encodeInto 的空间,避免无法回溯
// 额外分配 3 字节,避免 encodeInto 无法写入最后一个字符
this.ensureCapacity(maxUsage + 5 + 3);
// 预估头部大小
const headerSize = strLength < 0x80 ? 2 : strLength < 0x8000 ? 3 : 5;
const { length: headerPos, data, view } = this;
const bufLength = encodeInto(value, data, headerPos + headerSize);
if (bufLength < 0x80) {
view.setInt16(headerPos, I8_MASK | bufLength);
this.length = headerPos + 2 + bufLength;
}
else if (bufLength < 0x8000) {
if (headerSize < 3) {
data.copyWithin(headerPos + 3, headerPos + headerSize, headerPos + headerSize + bufLength);
}
data[headerPos] = 73 /* constants.INT16 */;
view.setInt16(headerPos + 1, bufLength);
this.length = headerPos + 3 + bufLength;
}
else {
if (headerSize < 5) {
data.copyWithin(headerPos + 5, headerPos + headerSize, headerPos + headerSize + bufLength);
}
data[headerPos] = 108 /* constants.INT32 */;
view.setInt32(headerPos + 1, bufLength);
this.length = headerPos + 5 + bufLength;
}
}
/** 写入大字符串 */
writeLargeStringData(value) {
const binLen = stringByteLength(value);
this.ensureCapacity(5);
this.data[this.length++] = 108 /* constants.INT32 */;
this.view.setInt32(this.length, binLen);
this.length += 4;
this.ensureCapacity(binLen);
encodeInto(value, this.data, this.length);
this.length += binLen;
}
/** 写入数组 */
writeLargeTypedArrayData(type, value) {
writeTypedArrayData(this, type, value);
}
}
//# sourceMappingURL=encoder.js.map