diff --git a/src/json-pack/json/JsonDecoder.ts b/src/json-pack/json/JsonDecoder.ts index 7522fe960c..79d0a8d7f1 100644 --- a/src/json-pack/json/JsonDecoder.ts +++ b/src/json-pack/json/JsonDecoder.ts @@ -26,7 +26,7 @@ const escapedCharReplacer = (char: string) => { return char; }; -// Starts with "data:application/octet-stream;base64," - 64 61 74 61 3a 61 70 70 6c 69 63 61 74 69 6f 6e 2f 6f 63 74 65 74 2d 73 74 72 65 61 6d 3b 62 61 73 65 36 34 2c +// Starts with data:application/octet-stream;base64, - 64 61 74 61 3a 61 70 70 6c 69 63 61 74 69 6f 6e 2f 6f 63 74 65 74 2d 73 74 72 65 61 6d 3b 62 61 73 65 36 34 2c const hasBinaryPrefix = (u8: Uint8Array, x: number) => u8[x] === 0x64 && u8[x + 1] === 0x61 && @@ -66,6 +66,44 @@ const hasBinaryPrefix = (u8: Uint8Array, x: number) => u8[x + 35] === 0x34 && u8[x + 36] === 0x2c; +// Matches "data:application/cbor,base64;9w==" +const isUndefined = (u8: Uint8Array, x: number) => + // u8[x++] === 0x22 && // " + // u8[x++] === 0x64 && // d + u8[x++] === 0x61 && // a + u8[x++] === 0x74 && // t + u8[x++] === 0x61 && // a + u8[x++] === 0x3a && // : + u8[x++] === 0x61 && // a + u8[x++] === 0x70 && // p + u8[x++] === 0x70 && // p + u8[x++] === 0x6c && // l + u8[x++] === 0x69 && // i + u8[x++] === 0x63 && // c + u8[x++] === 0x61 && // a + u8[x++] === 0x74 && // t + u8[x++] === 0x69 && // i + u8[x++] === 0x6f && // o + u8[x++] === 0x6e && // n + u8[x++] === 0x2f && // / + u8[x++] === 0x63 && // c + u8[x++] === 0x62 && // b + u8[x++] === 0x6f && // o + u8[x++] === 0x72 && // r + u8[x++] === 0x2c && // , + u8[x++] === 0x62 && // b + u8[x++] === 0x61 && // a + u8[x++] === 0x73 && // s + u8[x++] === 0x65 && // e + u8[x++] === 0x36 && // 6 + u8[x++] === 0x34 && // 4 + u8[x++] === 0x3b && // ; + u8[x++] === 0x39 && // 9 + u8[x++] === 0x77 && // w + u8[x++] === 0x3d && // = + u8[x++] === 0x3d && // = + u8[x++] === 0x22; // " + const findEndingQuote = (uint8: Uint8Array, x: number): number => { const len = uint8.length; let char = uint8[x]; @@ -172,10 +210,19 @@ export class JsonDecoder implements BinaryJsonDecoder { const uint8 = reader.uint8; const char = uint8[x]; switch (char) { - case 34: // " - return uint8[x + 1] === 0x64 // d - ? this.tryReadBin() || this.readStr() - : this.readStr(); + case 34: { + // " + if (uint8[x + 1] === 0x64) { + // d + const bin = this.tryReadBin(); + if (bin) return bin; + if (isUndefined(uint8, x + 2)) { + reader.x = x + 35; + return undefined; + } + } + return this.readStr(); + } case 91: // [ return this.readArr(); case 102: // f diff --git a/src/json-pack/json/JsonEncoder.ts b/src/json-pack/json/JsonEncoder.ts index b7f8cf35b8..1506d46b56 100644 --- a/src/json-pack/json/JsonEncoder.ts +++ b/src/json-pack/json/JsonEncoder.ts @@ -32,6 +32,9 @@ export class JsonEncoder implements BinaryJsonEncoder, StreamingBinaryJsonEncode return this.writeObj(value as Record); } } + case 'undefined': { + return this.writeUndef(); + } default: return this.writeNull(); } @@ -41,6 +44,35 @@ export class JsonEncoder implements BinaryJsonEncoder, StreamingBinaryJsonEncode this.writer.u32(0x6e756c6c); // null } + public writeUndef(): void { + const writer = this.writer; + const length = 35; + writer.ensureCapacity(length); + // Write: "data:application/cbor,base64;9w==" + const view = writer.view; + let x = writer.x; + view.setUint32(x, 0x22_64_61_74); // "dat + x += 4; + view.setUint32(x, 0x61_3a_61_70); // a:ap + x += 4; + view.setUint32(x, 0x70_6c_69_63); // plic + x += 4; + view.setUint32(x, 0x61_74_69_6f); // atio + x += 4; + view.setUint32(x, 0x6e_2f_63_62); // n/cb + x += 4; + view.setUint32(x, 0x6f_72_2c_62); // or,b + x += 4; + view.setUint32(x, 0x61_73_65_36); // ase6 + x += 4; + view.setUint32(x, 0x34_3b_39_77); // 4;9w + x += 4; + view.setUint16(x, 0x3d_3d); // == + x += 2; + writer.uint8[x++] = 0x22; // " + writer.x = x; + } + public writeBoolean(bool: boolean): void { if (bool) this.writer.u32(0x74727565); // true else this.writer.u8u32(0x66, 0x616c7365); // false diff --git a/src/json-pack/json/__tests__/JsonDecoder.spec.ts b/src/json-pack/json/__tests__/JsonDecoder.spec.ts index b8b9d4e049..d8946a6a42 100644 --- a/src/json-pack/json/__tests__/JsonDecoder.spec.ts +++ b/src/json-pack/json/__tests__/JsonDecoder.spec.ts @@ -27,6 +27,22 @@ describe('null', () => { }); }); +describe('undefined', () => { + test('undefined', () => { + const encoder = new JsonEncoder(new Writer()); + const encoded = encoder.encode(undefined); + const decoded = decoder.read(encoded); + expect(decoded).toBe(undefined); + }); + + test('undefined in array', () => { + const encoder = new JsonEncoder(new Writer()); + const encoded = encoder.encode({foo: [1, undefined, -1]}); + const decoded = decoder.read(encoded); + expect(decoded).toEqual({foo: [1, undefined, -1]}); + }); +}); + describe('boolean', () => { test('true', () => { const data = Buffer.from('true', 'utf-8'); diff --git a/src/json-pack/json/__tests__/JsonEncoder.spec.ts b/src/json-pack/json/__tests__/JsonEncoder.spec.ts index ff7ff4034c..3c5d031b83 100644 --- a/src/json-pack/json/__tests__/JsonEncoder.spec.ts +++ b/src/json-pack/json/__tests__/JsonEncoder.spec.ts @@ -19,6 +19,20 @@ describe('null', () => { }); }); +describe('undefined', () => { + test('undefined', () => { + const encoded = encoder.encode(undefined); + const txt = Buffer.from(encoded).toString('utf-8'); + expect(txt).toBe('"data:application/cbor,base64;9w=="'); + }); + + test('undefined in object', () => { + const encoded = encoder.encode({foo: undefined}); + const txt = Buffer.from(encoded).toString('utf-8'); + expect(txt).toBe('{"foo":"data:application/cbor,base64;9w=="}'); + }); +}); + describe('boolean', () => { test('true', () => { assertEncoder(true);