mirror of
https://github.com/fluencelabs/assemblyscript-json
synced 2025-04-02 19:21:08 +00:00
189 lines
6.7 KiB
TypeScript
189 lines
6.7 KiB
TypeScript
|
|
||
|
|
||
|
/**
|
||
|
* Extend from this class to handle events from parser.
|
||
|
* Default implementation traverses whole object tree and does nothing.
|
||
|
*/
|
||
|
export abstract class BSONHandler {
|
||
|
setString(name: string, value: string): void {
|
||
|
}
|
||
|
|
||
|
setBoolean(name: string, value: bool): void {
|
||
|
}
|
||
|
|
||
|
setNull(name: string): void {
|
||
|
}
|
||
|
|
||
|
setInteger(name: string, value: i32): void {
|
||
|
}
|
||
|
|
||
|
setUint8Array(name: string, value: Uint8Array): void {
|
||
|
}
|
||
|
|
||
|
pushArray(name: string): bool {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
popArray(): void {
|
||
|
}
|
||
|
|
||
|
pushObject(name: string): bool {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
popObject(): void {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extend from this class to handle events from parser.
|
||
|
* This implementation crashes on every unimplemented set/push method
|
||
|
* to allow easier validation of input.
|
||
|
*/
|
||
|
export class ThrowingBSONHandler extends BSONHandler {
|
||
|
setString(name: string, value: string): void {
|
||
|
assert(false, 'Unexpected string field ' + name + ' : "' + value + '"');
|
||
|
}
|
||
|
|
||
|
setBoolean(name: string, value: bool): void {
|
||
|
assert(false, 'Unexpected boolean field ' + name + ' : ' + (value ? 'true' : 'false'));
|
||
|
}
|
||
|
|
||
|
setNull(name: string): void {
|
||
|
assert(false, 'Unexpected null field ' + name);
|
||
|
}
|
||
|
|
||
|
setInteger(name: string, value: i32): void {
|
||
|
let arr: Array<i32> = [value];
|
||
|
assert(false, 'Unexpected integer field ' + name + ' : ' + arr.toString());
|
||
|
}
|
||
|
|
||
|
setUint8Array(name: string, value: Uint8Array): void {
|
||
|
assert(false, 'Unexpected byte array field ' + name + ' : ' + bin2str(value));
|
||
|
}
|
||
|
|
||
|
pushArray(name: string): bool {
|
||
|
assert(false, 'Unexpected array field' + name);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
pushObject(name: string): bool {
|
||
|
assert(false, 'Unexpected object field ' + name);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export class BSONDecoder<BSONHandlerT extends BSONHandler> {
|
||
|
handler: BSONHandlerT;
|
||
|
readIndex: i32 = 0;
|
||
|
|
||
|
constructor(handler: BSONHandlerT) {
|
||
|
this.handler = handler;
|
||
|
}
|
||
|
|
||
|
deserialize(buffer: Uint8Array, startIndex: i32 = 0): void {
|
||
|
this.readIndex = startIndex;
|
||
|
|
||
|
assert(buffer.length >= 5, "Document error: Size < 5 bytes");
|
||
|
|
||
|
let size : i32 = buffer[this.readIndex++] | i32(buffer[this.readIndex++]) << 8 | i32(buffer[this.readIndex++]) << 16 | i32(buffer[this.readIndex++]) << 24;
|
||
|
assert(size <= buffer.length, "Document error: Size mismatch");
|
||
|
assert(buffer[buffer.length - 1] == 0x00, "Document error: Missing termination");
|
||
|
|
||
|
for (; ;) {
|
||
|
// get element type
|
||
|
let elementType = buffer[this.readIndex++]; // read type
|
||
|
if (elementType === 0) break; // zero means last byte, exit
|
||
|
|
||
|
// get element name
|
||
|
let end = this.readIndex;
|
||
|
for (; buffer[end] !== 0x00 && end < buffer.length; end++);
|
||
|
assert(end < buffer.length - 1, "Document error: Illegal key name");
|
||
|
let name = bin2str(buffer.subarray(this.readIndex, end));
|
||
|
this.readIndex = ++end; // skip terminating zero
|
||
|
|
||
|
switch (elementType) {
|
||
|
case 0x02: // BSON type: String
|
||
|
size = buffer[this.readIndex++] | i32(buffer[this.readIndex++]) << 8 | i32(buffer[this.readIndex++]) << 16 | i32(buffer[this.readIndex++]) << 24;
|
||
|
this.handler.setString(name, bin2str(buffer.subarray(this.readIndex, this.readIndex += size - 1)));
|
||
|
this.readIndex++;
|
||
|
break;
|
||
|
|
||
|
case 0x03: // BSON type: Document (Object)
|
||
|
size = buffer[this.readIndex] | i32(buffer[this.readIndex + 1]) << 8 | i32(buffer[this.readIndex + 2]) << 16 | i32(buffer[this.readIndex + 3]) << 24;
|
||
|
if (this.handler.pushObject(name)) {
|
||
|
this.deserialize(buffer, this.readIndex);
|
||
|
} else {
|
||
|
this.readIndex += size;
|
||
|
}
|
||
|
this.handler.popObject();
|
||
|
break;
|
||
|
|
||
|
case 0x04: // BSON type: Array
|
||
|
size = buffer[this.readIndex] | i32(buffer[this.readIndex + 1]) << 8 | i32(buffer[this.readIndex + 2]) << 16 | i32(buffer[this.readIndex + 3]) << 24; // NO 'i' increment since the size bytes are reread during the recursion
|
||
|
if (this.handler.pushArray(name)) {
|
||
|
this.deserialize(buffer, this.readIndex);
|
||
|
} else {
|
||
|
this.readIndex += size;
|
||
|
}
|
||
|
this.handler.popArray();
|
||
|
break;
|
||
|
|
||
|
case 0x05: // BSON type: Binary data
|
||
|
size = buffer[this.readIndex++] | i32(buffer[this.readIndex++]) << 8 | i32(buffer[this.readIndex++]) << 16 | i32(buffer[this.readIndex++]) << 24;
|
||
|
if (buffer[this.readIndex++] === 0x04) {
|
||
|
// BSON subtype: UUID (not supported)
|
||
|
return
|
||
|
}
|
||
|
this.handler.setUint8Array(name, buffer.subarray(this.readIndex, this.readIndex += size)); // use slice() here to get a new array
|
||
|
break;
|
||
|
|
||
|
case 0x08: // BSON type: Boolean
|
||
|
this.handler.setBoolean(name, buffer[this.readIndex++] === 1);
|
||
|
break;
|
||
|
|
||
|
case 0x0A: // BSON type: Null
|
||
|
this.handler.setNull(name);
|
||
|
break;
|
||
|
|
||
|
case 0x10: // BSON type: 32-bit integer
|
||
|
this.handler.setInteger(name, buffer[this.readIndex++] | i32(buffer[this.readIndex++]) << 8 | i32(buffer[this.readIndex++]) << 16 | i32(buffer[this.readIndex++]) << 24);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
assert(false, "Parsing error: Unknown element");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Parse byte array as an UTF-8 string
|
||
|
* @param {Uint8Array} bin UTF-8 text given as array of bytes
|
||
|
* @return {String} UTF-8 Text string
|
||
|
*/
|
||
|
export function bin2str(bin: Uint8Array): string {
|
||
|
let str = '', len = bin.length, i = 0;
|
||
|
let c: i32, c2: i32, c3: i32;
|
||
|
|
||
|
while (i < len) {
|
||
|
c = bin[i];
|
||
|
if (c < 128) {
|
||
|
str += String.fromCharCode(c);
|
||
|
i++;
|
||
|
}
|
||
|
else if ((c > 191) && (c < 224)) {
|
||
|
c2 = bin[i + 1];
|
||
|
str += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
|
||
|
i += 2;
|
||
|
}
|
||
|
else {
|
||
|
c2 = bin[i + 1];
|
||
|
c3 = bin[i + 2];
|
||
|
str += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
|
||
|
i += 3;
|
||
|
}
|
||
|
}
|
||
|
return str;
|
||
|
}
|