Skip to content

Commit 80d9f4a

Browse files
authored
Merge pull request #549 from streamich/dag-json
DAG-JSON
2 parents 3b882fb + 1ff8765 commit 80d9f4a

20 files changed

+536
-83
lines changed

src/json-pack/cbor/CborEncoderStable.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import {CborEncoder} from './CborEncoder';
22
import {sort} from '../../util/sort/insertion2';
33
import {MAJOR_OVERLAY} from './constants';
4-
5-
const objectKeyComparator = (a: string, b: string): number => {
6-
const len1 = a.length;
7-
const len2 = b.length;
8-
return len1 === len2 ? (a > b ? 1 : -1) : len1 - len2;
9-
};
4+
import {objKeyCmp} from '../util/objKeyCmp';
105

116
const strHeaderLength = (strSize: number): 1 | 2 | 3 | 5 => {
127
if (strSize <= 23) return 1;
@@ -18,7 +13,7 @@ const strHeaderLength = (strSize: number): 1 | 2 | 3 | 5 => {
1813
export class CborEncoderStable extends CborEncoder {
1914
public writeObj(obj: Record<string, unknown>): void {
2015
const keys = Object.keys(obj);
21-
sort(keys, objectKeyComparator);
16+
sort(keys, objKeyCmp);
2217
const length = keys.length;
2318
this.writeObjHdr(length);
2419
for (let i = 0; i < length; i++) {

src/json-pack/json/JsonDecoder.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {decodeUtf8} from '../../util/buffers/utf8/decodeUtf8';
22
import {Reader} from '../../util/buffers/Reader';
33
import {fromBase64Bin} from '../../util/base64/fromBase64Bin';
4+
import {findEndingQuote} from './util';
45
import type {BinaryJsonDecoder, PackValue} from '../types';
56

67
const REGEX_REPLACE_ESCAPED_CHARS = /\\(b|f|n|r|t|"|\/|\\)/g;
@@ -104,20 +105,6 @@ const isUndefined = (u8: Uint8Array, x: number) =>
104105
u8[x++] === 0x3d && // =
105106
u8[x++] === 0x22; // "
106107

107-
const findEndingQuote = (uint8: Uint8Array, x: number): number => {
108-
const len = uint8.length;
109-
let char = uint8[x];
110-
let prev = 0;
111-
while (x < len) {
112-
if (char === 34 && prev !== 92) break;
113-
if (char === 92 && prev === 92) prev = 0;
114-
else prev = char;
115-
char = uint8[++x];
116-
}
117-
if (x === len) throw new Error('Invalid JSON');
118-
return x;
119-
};
120-
121108
const fromCharCode = String.fromCharCode;
122109

123110
const readShortUtf8StrAndUnescape = (reader: Reader): string => {
@@ -198,7 +185,7 @@ const readShortUtf8StrAndUnescape = (reader: Reader): string => {
198185
export class JsonDecoder implements BinaryJsonDecoder {
199186
public reader = new Reader();
200187

201-
public read(uint8: Uint8Array): PackValue {
188+
public read(uint8: Uint8Array): unknown {
202189
this.reader.reset(uint8);
203190
return this.readAny();
204191
}
@@ -208,7 +195,7 @@ export class JsonDecoder implements BinaryJsonDecoder {
208195
return this.readAny();
209196
}
210197

211-
public readAny(): PackValue {
198+
public readAny(): unknown {
212199
this.skipWhitespace();
213200
const reader = this.reader;
214201
const x = reader.x;
@@ -653,10 +640,10 @@ export class JsonDecoder implements BinaryJsonDecoder {
653640
return bin;
654641
}
655642

656-
public readArr(): PackValue[] {
643+
public readArr(): unknown[] {
657644
const reader = this.reader;
658645
if (reader.u8() !== 0x5b) throw new Error('Invalid JSON');
659-
const arr: PackValue[] = [];
646+
const arr: unknown[] = [];
660647
const uint8 = reader.uint8;
661648
while (true) {
662649
this.skipWhitespace();
@@ -670,10 +657,10 @@ export class JsonDecoder implements BinaryJsonDecoder {
670657
}
671658
}
672659

673-
public readObj(): Record<string, PackValue> {
660+
public readObj(): PackValue | Record<string, unknown> | unknown {
674661
const reader = this.reader;
675662
if (reader.u8() !== 0x7b) throw new Error('Invalid JSON');
676-
const obj: Record<string, PackValue> = {};
663+
const obj: Record<string, unknown> = {};
677664
const uint8 = reader.uint8;
678665
while (true) {
679666
this.skipWhitespace();
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import {JsonDecoder} from './JsonDecoder';
2+
import {findEndingQuote} from './util';
3+
import type {PackValue} from '../types';
4+
import {createFromBase64Bin} from '../../util/base64/createFromBase64Bin';
5+
6+
export const fromBase64Bin = createFromBase64Bin(undefined, '');
7+
8+
export class JsonDecoderDag extends JsonDecoder {
9+
public readObj(): PackValue | Record<string, PackValue> | Uint8Array | unknown {
10+
const bytes = this.tryReadBytes();
11+
if (bytes) return bytes;
12+
const cid = this.tryReadCid();
13+
if (cid) return cid;
14+
return super.readObj();
15+
}
16+
17+
protected tryReadBytes(): Uint8Array | undefined {
18+
const reader = this.reader;
19+
const x = reader.x;
20+
if (reader.u8() !== 0x7b) {
21+
// {
22+
reader.x = x;
23+
return;
24+
}
25+
this.skipWhitespace();
26+
if (reader.u8() !== 0x22 || reader.u8() !== 0x2f || reader.u8() !== 0x22) {
27+
// "/"
28+
reader.x = x;
29+
return;
30+
}
31+
this.skipWhitespace();
32+
if (reader.u8() !== 0x3a) {
33+
// :
34+
reader.x = x;
35+
return;
36+
}
37+
this.skipWhitespace();
38+
if (reader.u8() !== 0x7b) {
39+
// {
40+
reader.x = x;
41+
return;
42+
}
43+
this.skipWhitespace();
44+
if (
45+
reader.u8() !== 0x22 ||
46+
reader.u8() !== 0x62 ||
47+
reader.u8() !== 0x79 ||
48+
reader.u8() !== 0x74 ||
49+
reader.u8() !== 0x65 ||
50+
reader.u8() !== 0x73 ||
51+
reader.u8() !== 0x22
52+
) {
53+
// "bytes"
54+
reader.x = x;
55+
return;
56+
}
57+
this.skipWhitespace();
58+
if (reader.u8() !== 0x3a) {
59+
// :
60+
reader.x = x;
61+
return;
62+
}
63+
this.skipWhitespace();
64+
if (reader.u8() !== 0x22) {
65+
// "
66+
reader.x = x;
67+
return;
68+
}
69+
const bufStart = reader.x;
70+
const bufEnd = findEndingQuote(reader.uint8, bufStart);
71+
reader.x = 1 + bufEnd;
72+
this.skipWhitespace();
73+
if (reader.u8() !== 0x7d) {
74+
// }
75+
reader.x = x;
76+
return;
77+
}
78+
this.skipWhitespace();
79+
if (reader.u8() !== 0x7d) {
80+
// }
81+
reader.x = x;
82+
return;
83+
}
84+
const bin = fromBase64Bin(reader.view, bufStart, bufEnd - bufStart);
85+
return bin;
86+
}
87+
88+
protected tryReadCid(): undefined | unknown {
89+
const reader = this.reader;
90+
const x = reader.x;
91+
if (reader.u8() !== 0x7b) {
92+
// {
93+
reader.x = x;
94+
return;
95+
}
96+
this.skipWhitespace();
97+
if (reader.u8() !== 0x22 || reader.u8() !== 0x2f || reader.u8() !== 0x22) {
98+
// "/"
99+
reader.x = x;
100+
return;
101+
}
102+
this.skipWhitespace();
103+
if (reader.u8() !== 0x3a) {
104+
// :
105+
reader.x = x;
106+
return;
107+
}
108+
this.skipWhitespace();
109+
if (reader.u8() !== 0x22) {
110+
// "
111+
reader.x = x;
112+
return;
113+
}
114+
const bufStart = reader.x;
115+
const bufEnd = findEndingQuote(reader.uint8, bufStart);
116+
reader.x = 1 + bufEnd;
117+
this.skipWhitespace();
118+
if (reader.u8() !== 0x7d) {
119+
// }
120+
reader.x = x;
121+
return;
122+
}
123+
const finalX = reader.x;
124+
reader.x = bufStart;
125+
const cid = reader.ascii(bufEnd - bufStart);
126+
reader.x = finalX;
127+
return this.readCid(cid);
128+
}
129+
130+
public readCid(cid: string): unknown {
131+
return cid;
132+
}
133+
}

src/json-pack/json/JsonEncoder.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ export class JsonEncoder implements BinaryJsonEncoder, StreamingBinaryJsonEncode
1212
return writer.flush();
1313
}
1414

15+
/**
16+
* Called when the encoder encounters a value that it does not know how to encode.
17+
*
18+
* @param value Some JavaScript value.
19+
*/
20+
public writeUnknown(value: unknown): void {
21+
this.writeNull();
22+
}
23+
1524
public writeAny(value: unknown): void {
1625
switch (typeof value) {
1726
case 'boolean':
@@ -24,19 +33,21 @@ export class JsonEncoder implements BinaryJsonEncoder, StreamingBinaryJsonEncode
2433
if (value === null) return this.writeNull();
2534
const constructor = value.constructor;
2635
switch (constructor) {
36+
case Object:
37+
return this.writeObj(value as Record<string, unknown>);
2738
case Array:
2839
return this.writeArr(value as unknown[]);
2940
case Uint8Array:
3041
return this.writeBin(value as Uint8Array);
3142
default:
32-
return this.writeObj(value as Record<string, unknown>);
43+
return this.writeUnknown(value);
3344
}
3445
}
3546
case 'undefined': {
3647
return this.writeUndef();
3748
}
3849
default:
39-
return this.writeNull();
50+
return this.writeUnknown(value);
4051
}
4152
}
4253

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {JsonEncoderStable} from './JsonEncoderStable';
2+
import {createToBase64Bin} from '../../util/base64/createToBase64Bin';
3+
4+
const objBaseLength = '{"/":{"bytes":""}}'.length;
5+
const cidBaseLength = '{"/":""}'.length;
6+
const base64Encode = createToBase64Bin(undefined, '');
7+
8+
/**
9+
* Base class for implementing DAG-JSON encoders.
10+
*
11+
* @see https://ipld.io/specs/codecs/dag-json/spec/
12+
*/
13+
export class JsonEncoderDag extends JsonEncoderStable {
14+
/**
15+
* Encodes binary data as nested `["/", "bytes"]` object encoded in Base64
16+
* without padding.
17+
*
18+
* Example:
19+
*
20+
* ```json
21+
* {"/":{"bytes":"aGVsbG8gd29ybGQ"}}
22+
* ```
23+
*
24+
* @param buf Binary data to write.
25+
*/
26+
public writeBin(buf: Uint8Array): void {
27+
const writer = this.writer;
28+
const length = buf.length;
29+
writer.ensureCapacity(objBaseLength + (length << 1));
30+
const view = writer.view;
31+
const uint8 = writer.uint8;
32+
let x = writer.x;
33+
view.setUint32(x, 0x7b222f22); // {"/"
34+
x += 4;
35+
view.setUint32(x, 0x3a7b2262); // :{"b
36+
x += 4;
37+
view.setUint32(x, 0x79746573); // ytes
38+
x += 4;
39+
view.setUint16(x, 0x223a); // ":
40+
x += 2;
41+
uint8[x] = 0x22; // "
42+
x += 1;
43+
x = base64Encode(buf, 0, length, view, x);
44+
view.setUint16(x, 0x227d); // "}
45+
x += 2;
46+
uint8[x] = 0x7d; // }
47+
x += 1;
48+
writer.x = x;
49+
}
50+
51+
public writeCid(cid: string): void {
52+
const writer = this.writer;
53+
writer.ensureCapacity(cidBaseLength + cid.length);
54+
writer.u32(0x7b222f22); // {"/"
55+
writer.u16(0x3a22); // :"
56+
writer.ascii(cid);
57+
writer.u16(0x227d); // "}
58+
}
59+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {JsonEncoder} from './JsonEncoder';
2+
import {sort} from '../../util/sort/insertion2';
3+
import {objKeyCmp} from '../util/objKeyCmp';
4+
5+
export class JsonEncoderStable extends JsonEncoder {
6+
public writeObj(obj: Record<string, unknown>): void {
7+
const writer = this.writer;
8+
const keys = Object.keys(obj);
9+
sort(keys, objKeyCmp);
10+
const length = keys.length;
11+
if (!length) return writer.u16(0x7b7d); // {}
12+
writer.u8(0x7b); // {
13+
for (let i = 0; i < length; i++) {
14+
const key = keys[i];
15+
const value = obj[key];
16+
this.writeStr(key);
17+
writer.u8(0x3a); // :
18+
this.writeAny(value);
19+
writer.u8(0x2c); // ,
20+
}
21+
writer.uint8[writer.x - 1] = 0x7d; // }
22+
}
23+
}

0 commit comments

Comments
 (0)