Skip to content

Commit 65bed01

Browse files
committed
add JSON support
1 parent d6c418a commit 65bed01

File tree

2 files changed

+162
-25
lines changed

2 files changed

+162
-25
lines changed

src/lib/v8-derialization/deserialize.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,19 @@ describe("V8 Deserialization", () => {
4949
it("big number", () => {
5050
expect(p("FF0F5A10D20A1FEB8CA954AB").value).toBe(12345678901234567890n);
5151
});
52+
53+
it("array", () => {
54+
expect(p("FF0F4103220568656C6C6F2205776F726C64490A240003").value).toEqual([
55+
"hello",
56+
"world",
57+
5,
58+
]);
59+
});
60+
61+
it("object", () => {
62+
expect(
63+
p("FF0F6F220568656C6C6F2205776F726C6422066E756D626572490A7B02")
64+
.value as Record<string, unknown>
65+
).toEqual({ hello: "world", number: 5 });
66+
});
5267
});

src/lib/v8-derialization/index.ts

Lines changed: 147 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -86,39 +86,161 @@ function deserializeValue(df: DataView, offset: number): [unknown, number] {
8686
}
8787
// BigInt
8888
case "Z".charCodeAt(0): {
89-
// Read the bitfield which contains both sign and length information
90-
const [bitfield, bytesReadForBitfield] = decodeVarint(df, offset + 1);
89+
return deserializeBigInt(df, offset + 1);
90+
}
91+
case "A".charCodeAt(0): {
92+
return deserializeDenseJSArray(df, offset + 1);
93+
}
94+
case "o".charCodeAt(0): {
95+
// JavaScript Object
96+
return deserializeJSObject(df, offset + 1);
97+
}
98+
99+
default:
100+
throw new Error(
101+
`Unsupported type: ${String.fromCharCode(type)} (${type})`
102+
);
103+
}
104+
}
105+
106+
/**
107+
* Deserializes a JavaScript object
108+
* @param df DataView containing the serialized data
109+
* @param offset Current offset in the DataView (after the type byte)
110+
*/
111+
function deserializeJSObject(
112+
df: DataView,
113+
offset: number
114+
): [Record<string | number, unknown>, number] {
115+
const obj: Record<string | number, unknown> = {};
116+
let currentOffset = offset;
117+
let propertyCount = 0;
118+
119+
// Keep reading key-value pairs until we find the end marker
120+
while (df.getUint8(currentOffset) !== "{".charCodeAt(0)) {
121+
// Read property key
122+
const [key, keyOffset] = deserializeValue(df, currentOffset);
123+
currentOffset = keyOffset;
124+
125+
// Read property value
126+
const [value, valueOffset] = deserializeValue(df, currentOffset);
127+
currentOffset = valueOffset;
128+
129+
// Set property on object
130+
if (typeof key === "string" || typeof key === "number") {
131+
obj[key] = value;
132+
}
133+
134+
propertyCount++;
135+
}
136+
137+
// Skip the end marker
138+
currentOffset++;
139+
140+
// Read numProperties for validation
141+
const [numProperties, propBytesRead] = decodeVarint(df, currentOffset);
142+
currentOffset += propBytesRead;
143+
144+
// Validate that we read the expected number of properties
145+
if (propertyCount !== numProperties) {
146+
throw new Error(
147+
`Expected ${numProperties} properties, but read ${propertyCount}`
148+
);
149+
}
150+
151+
return [obj, currentOffset];
152+
}
153+
154+
/**
155+
* Deserializes a dense JavaScript array
156+
* @param df DataView containing the serialized data
157+
* @param offset Current offset in the DataView (after the type byte)
158+
*/
159+
function deserializeDenseJSArray(
160+
df: DataView,
161+
offset: number
162+
): [unknown[], number] {
163+
const [length, bytesRead] = decodeVarint(df, offset);
164+
const array: unknown[] = [];
165+
let currentOffset = offset + bytesRead;
166+
167+
// Read each array element
168+
for (let i = 0; i < length; i++) {
169+
const [value, nextOffset] = deserializeValue(df, currentOffset);
170+
array.push(value);
171+
currentOffset = nextOffset;
172+
}
91173

92-
// Extract sign from the bitfield (lowest bit is the sign: 0 = positive, 1 = negative)
93-
const isNegative = (bitfield & 1) === 1;
174+
// Check for the end marker
175+
if (df.getUint8(currentOffset) !== "$".charCodeAt(0)) {
176+
throw new Error("Expected end of array marker");
177+
}
94178

95-
// Calculate the byte length (based on V8's implementation)
96-
// The length is stored in the upper bits of the bitfield
97-
const byteLength = Math.floor(bitfield / 2); // Simplified approximation
179+
// Skip the end marker
180+
currentOffset++;
98181

99-
// Read the bytes for the BigInt digits
100-
const digitsStart = offset + 1 + bytesReadForBitfield;
101-
let bigintValue = 0n;
182+
// Read numProperties and length
183+
const [numProperties, propBytesRead] = decodeVarint(df, currentOffset);
184+
currentOffset += propBytesRead;
102185

103-
// Read each byte and build the BigInt
104-
for (let i = 0; i < byteLength; i++) {
105-
// BigInts are stored in little-endian format in V8
106-
const byte = df.getUint8(digitsStart + i);
107-
bigintValue = bigintValue + (BigInt(byte) << BigInt(8 * i));
108-
}
186+
const [arrayLength, lengthBytesRead] = decodeVarint(df, currentOffset);
187+
currentOffset += lengthBytesRead;
109188

110-
// Apply sign
111-
if (isNegative) {
112-
bigintValue = -bigintValue;
113-
}
189+
// Read properties if any (key-value pairs)
190+
for (let i = 0; i < numProperties; i++) {
191+
// Read property key
192+
const [key, keyOffset] = deserializeValue(df, currentOffset);
193+
currentOffset = keyOffset;
114194

115-
return [bigintValue, digitsStart + byteLength];
195+
// Read property value
196+
const [value, valueOffset] = deserializeValue(df, currentOffset);
197+
currentOffset = valueOffset;
198+
199+
// Set property on array
200+
if (typeof key === "string" || typeof key === "number") {
201+
(array as any)[key] = value;
116202
}
117-
default:
118-
throw new Error(
119-
`Unsupported type: ${String.fromCharCode(type)} (${type})`
120-
);
121203
}
204+
205+
// Set the array length to match the serialized length
206+
array.length = arrayLength;
207+
208+
return [array, currentOffset];
209+
}
210+
211+
/**
212+
* Deserializes a BigInt value
213+
* @param df DataView containing the serialized data
214+
* @param offset Current offset in the DataView (after the type byte)
215+
*/
216+
function deserializeBigInt(df: DataView, offset: number): [bigint, number] {
217+
// Read the bitfield which contains both sign and length information
218+
const [bitfield, bytesReadForBitfield] = decodeVarint(df, offset);
219+
220+
// Extract sign from the bitfield (lowest bit is the sign: 0 = positive, 1 = negative)
221+
const isNegative = (bitfield & 1) === 1;
222+
223+
// Calculate the byte length (based on V8's implementation)
224+
// The length is stored in the upper bits of the bitfield
225+
const byteLength = Math.floor(bitfield / 2); // Simplified approximation
226+
227+
// Read the bytes for the BigInt digits
228+
const digitsStart = offset + bytesReadForBitfield;
229+
let bigintValue = 0n;
230+
231+
// Read each byte and build the BigInt
232+
for (let i = 0; i < byteLength; i++) {
233+
// BigInts are stored in little-endian format in V8
234+
const byte = df.getUint8(digitsStart + i);
235+
bigintValue = bigintValue + (BigInt(byte) << BigInt(8 * i));
236+
}
237+
238+
// Apply sign
239+
if (isNegative) {
240+
bigintValue = -bigintValue;
241+
}
242+
243+
return [bigintValue, digitsStart + byteLength];
122244
}
123245

124246
/**

0 commit comments

Comments
 (0)