Skip to content

Commit

Permalink
Merge pull request #507 from streamich/json-undefined
Browse files Browse the repository at this point in the history
Support `undefined` value in JSON codec
  • Loading branch information
streamich authored Mar 3, 2024
2 parents b0243b8 + be1486c commit 01220e8
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 5 deletions.
57 changes: 52 additions & 5 deletions src/json-pack/json/JsonDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions src/json-pack/json/JsonEncoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export class JsonEncoder implements BinaryJsonEncoder, StreamingBinaryJsonEncode
return this.writeObj(value as Record<string, unknown>);
}
}
case 'undefined': {
return this.writeUndef();
}
default:
return this.writeNull();
}
Expand All @@ -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
Expand Down
16 changes: 16 additions & 0 deletions src/json-pack/json/__tests__/JsonDecoder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
14 changes: 14 additions & 0 deletions src/json-pack/json/__tests__/JsonEncoder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 01220e8

Please sign in to comment.