Skip to content

Commit da4147e

Browse files
committed
fix: add typed array and ArrayBuffer helpers
1 parent 27e23ae commit da4147e

11 files changed

Lines changed: 542 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
### fix: add built-in typed array support
6+
- Added default serialization and deserialization support for all built-in typed arrays.
7+
- Added dedicated `ArrayBuffer` parse/serialize helpers and routed transform-generated `ArrayBuffer` field serialization through a dedicated buffer path inside `@json` classes.
8+
- Added regression coverage for direct typed-array round-trips, explicit `ArrayBuffer` helpers, field-initialized `@json` classes with `ArrayBuffer`, constructor-assigned typed-array fields, and nested `@json` class payloads.
9+
510
### fix: finish subtype-aware StaticArray deserialization
611
- Reworked `StaticArray` deserialization to dispatch by element subtype, mirroring the existing `Array<subtype>` deserializer matrix instead of relying on a generic fallback.
712
- Materialized complex deserialized array results into fixed `StaticArray` storage for nested arrays, nested static arrays, maps, `JSON.Value`, `JSON.Box`, `JSON.Obj`, `JSON.Raw`, and transform-backed structs.
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import { JSON } from "..";
2+
import { describe, expect } from "as-test";
3+
4+
function makeInt8Array(): Int8Array {
5+
const out = new Int8Array(3);
6+
out[0] = -1;
7+
out[1] = 0;
8+
out[2] = 127;
9+
return out;
10+
}
11+
12+
function makeUint8Array(): Uint8Array {
13+
const out = new Uint8Array(4);
14+
out[0] = 0;
15+
out[1] = 1;
16+
out[2] = 2;
17+
out[3] = 255;
18+
return out;
19+
}
20+
21+
function makeUint8ClampedArray(): Uint8ClampedArray {
22+
const out = new Uint8ClampedArray(3);
23+
out[0] = 0;
24+
out[1] = 128;
25+
out[2] = 255;
26+
return out;
27+
}
28+
29+
function makeInt16Array(): Int16Array {
30+
const out = new Int16Array(3);
31+
out[0] = -32768;
32+
out[1] = 0;
33+
out[2] = 32767;
34+
return out;
35+
}
36+
37+
function makeUint16Array(): Uint16Array {
38+
const out = new Uint16Array(3);
39+
out[0] = 0;
40+
out[1] = 42;
41+
out[2] = 65535;
42+
return out;
43+
}
44+
45+
function makeInt32Array(): Int32Array {
46+
const out = new Int32Array(3);
47+
out[0] = -2147483648;
48+
out[1] = 0;
49+
out[2] = 2147483647;
50+
return out;
51+
}
52+
53+
function makeUint32Array(): Uint32Array {
54+
const out = new Uint32Array(3);
55+
out[0] = 0;
56+
out[1] = 42;
57+
out[2] = 4294967295;
58+
return out;
59+
}
60+
61+
function makeInt64Array(): Int64Array {
62+
const out = new Int64Array(3);
63+
out[0] = -9007199254740991;
64+
out[1] = 0;
65+
out[2] = 9007199254740991;
66+
return out;
67+
}
68+
69+
function makeUint64Array(): Uint64Array {
70+
const out = new Uint64Array(3);
71+
out[0] = 0;
72+
out[1] = 42;
73+
out[2] = 9007199254740991;
74+
return out;
75+
}
76+
77+
function makeFloat32Array(): Float32Array {
78+
const out = new Float32Array(3);
79+
out[0] = -1.5;
80+
out[1] = 0.25;
81+
out[2] = 3.75;
82+
return out;
83+
}
84+
85+
function makeFloat64Array(): Float64Array {
86+
const out = new Float64Array(3);
87+
out[0] = -1.5;
88+
out[1] = 0.125;
89+
out[2] = 3.14159;
90+
return out;
91+
}
92+
93+
function makeArrayBuffer(): ArrayBuffer {
94+
const out = new ArrayBuffer(4);
95+
const view = Uint8Array.wrap(out);
96+
view[0] = 10;
97+
view[1] = 20;
98+
view[2] = 30;
99+
view[3] = 40;
100+
return out;
101+
}
102+
103+
104+
@json
105+
class BinaryEnvelope {
106+
bytes: Uint8Array = makeUint8Array();
107+
ints: Int16Array = makeInt16Array();
108+
floats: Float32Array = makeFloat32Array();
109+
raw: ArrayBuffer = makeArrayBuffer();
110+
}
111+
112+
113+
@json
114+
class BinaryEnvelopeCtor {
115+
bytes: Uint8Array;
116+
ints: Int16Array;
117+
floats: Float32Array;
118+
119+
constructor() {
120+
this.bytes = makeUint8Array();
121+
this.ints = makeInt16Array();
122+
this.floats = makeFloat32Array();
123+
}
124+
}
125+
126+
127+
@json
128+
class BinaryContainer {
129+
left: BinaryEnvelopeCtor = new BinaryEnvelopeCtor();
130+
right: BinaryEnvelope = new BinaryEnvelope();
131+
}
132+
133+
describe("Should serialize and deserialize typed arrays by default", () => {
134+
const int8 = makeInt8Array();
135+
expect(JSON.stringify(int8)).toBe("[-1,0,127]");
136+
expect(JSON.stringify(JSON.parse<Int8Array>("[-1,0,127]"))).toBe(JSON.stringify(int8));
137+
138+
const uint8 = makeUint8Array();
139+
expect(JSON.stringify(uint8)).toBe("[0,1,2,255]");
140+
expect(JSON.stringify(JSON.parse<Uint8Array>("[0,1,2,255]"))).toBe(JSON.stringify(uint8));
141+
142+
const uint8Clamped = makeUint8ClampedArray();
143+
expect(JSON.stringify(uint8Clamped)).toBe("[0,128,255]");
144+
expect(JSON.stringify(JSON.parse<Uint8ClampedArray>("[0,128,255]"))).toBe(JSON.stringify(uint8Clamped));
145+
146+
const int16 = makeInt16Array();
147+
expect(JSON.stringify(int16)).toBe("[-32768,0,32767]");
148+
expect(JSON.stringify(JSON.parse<Int16Array>("[-32768,0,32767]"))).toBe(JSON.stringify(int16));
149+
150+
const uint16 = makeUint16Array();
151+
expect(JSON.stringify(uint16)).toBe("[0,42,65535]");
152+
expect(JSON.stringify(JSON.parse<Uint16Array>("[0,42,65535]"))).toBe(JSON.stringify(uint16));
153+
154+
const int32 = makeInt32Array();
155+
expect(JSON.stringify(int32)).toBe("[-2147483648,0,2147483647]");
156+
expect(JSON.stringify(JSON.parse<Int32Array>("[-2147483648,0,2147483647]"))).toBe(JSON.stringify(int32));
157+
158+
const uint32 = makeUint32Array();
159+
expect(JSON.stringify(uint32)).toBe("[0,42,4294967295]");
160+
expect(JSON.stringify(JSON.parse<Uint32Array>("[0,42,4294967295]"))).toBe(JSON.stringify(uint32));
161+
162+
const int64 = makeInt64Array();
163+
expect(JSON.stringify(int64)).toBe("[-9007199254740991,0,9007199254740991]");
164+
expect(JSON.stringify(JSON.parse<Int64Array>("[-9007199254740991,0,9007199254740991]"))).toBe(JSON.stringify(int64));
165+
166+
const uint64 = makeUint64Array();
167+
expect(JSON.stringify(uint64)).toBe("[0,42,9007199254740991]");
168+
expect(JSON.stringify(JSON.parse<Uint64Array>("[0,42,9007199254740991]"))).toBe(JSON.stringify(uint64));
169+
170+
const float32 = makeFloat32Array();
171+
expect(JSON.stringify(float32)).toBe("[-1.5,0.25,3.75]");
172+
expect(JSON.stringify(JSON.parse<Float32Array>("[-1.5,0.25,3.75]"))).toBe(JSON.stringify(float32));
173+
174+
const float64 = makeFloat64Array();
175+
expect(JSON.stringify(float64)).toBe("[-1.5,0.125,3.14159]");
176+
expect(JSON.stringify(JSON.parse<Float64Array>("[-1.5,0.125,3.14159]"))).toBe(JSON.stringify(float64));
177+
});
178+
179+
describe("Should serialize and deserialize ArrayBuffer by default", () => {
180+
const buffer = makeArrayBuffer();
181+
expect(JSON.stringifyArrayBuffer(changetype<usize>(buffer))).toBe("[10,20,30,40]");
182+
183+
const parsed = JSON.parseArrayBuffer("[10,20,30,40]");
184+
expect(JSON.stringifyArrayBuffer(changetype<usize>(parsed))).toBe(JSON.stringifyArrayBuffer(changetype<usize>(buffer)));
185+
});
186+
187+
describe("Should support typed arrays and ArrayBuffer inside @json classes", () => {
188+
const input = new BinaryEnvelope();
189+
const serialized = JSON.stringify(input);
190+
expect(serialized).toBe('{"bytes":[0,1,2,255],"ints":[-32768,0,32767],"floats":[-1.5,0.25,3.75],"raw":[10,20,30,40]}');
191+
192+
const parsed = JSON.parse<BinaryEnvelope>(serialized);
193+
expect(JSON.stringify(parsed)).toBe(serialized);
194+
expect(JSON.stringify(parsed.bytes)).toBe(JSON.stringify(input.bytes));
195+
expect(JSON.stringify(parsed.ints)).toBe(JSON.stringify(input.ints));
196+
expect(JSON.stringify(parsed.floats)).toBe(JSON.stringify(input.floats));
197+
expect(JSON.stringifyArrayBuffer(changetype<usize>(parsed.raw))).toBe(JSON.stringifyArrayBuffer(changetype<usize>(input.raw)));
198+
});
199+
200+
describe("Should serialize constructor-assigned typed arrays inside @json classes", () => {
201+
const input = new BinaryEnvelopeCtor();
202+
const serialized = JSON.stringify(input);
203+
expect(serialized).toBe('{"bytes":[0,1,2,255],"ints":[-32768,0,32767],"floats":[-1.5,0.25,3.75]}');
204+
});
205+
206+
describe("Should serialize nested classes with mixed typed-array field initialization styles", () => {
207+
const input = new BinaryContainer();
208+
const serialized = JSON.stringify(input);
209+
expect(serialized).toBe('{"left":{"bytes":[0,1,2,255],"ints":[-32768,0,32767],"floats":[-1.5,0.25,3.75]},"right":{"bytes":[0,1,2,255],"ints":[-32768,0,32767],"floats":[-1.5,0.25,3.75],"raw":[10,20,30,40]}}');
210+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { deserializeArrayBuffer, deserializeTypedArray } from "../simple/typedarray";
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { COMMA, BRACKET_RIGHT } from "../../custom/chars";
2+
import { deserializeFloat } from "../index/float";
3+
import { atoi, isSpace } from "../../util";
4+
5+
6+
@inline function countTypedArrayElements(srcStart: usize, srcEnd: usize): i32 {
7+
let count = 0;
8+
9+
while (srcStart < srcEnd) {
10+
const code = load<u16>(srcStart);
11+
if (code - 48 <= 9 || code == 45) {
12+
count++;
13+
srcStart += 2;
14+
15+
while (srcStart < srcEnd) {
16+
const code = load<u16>(srcStart);
17+
if (code == COMMA || code == BRACKET_RIGHT || isSpace(code)) break;
18+
srcStart += 2;
19+
}
20+
}
21+
22+
srcStart += 2;
23+
}
24+
25+
return count;
26+
}
27+
28+
export function deserializeTypedArray<T extends ArrayLike<number>>(srcStart: usize, srcEnd: usize, dst: usize = 0): T {
29+
const count = countTypedArrayElements(srcStart, srcEnd);
30+
let out = changetype<T>(dst || changetype<usize>(instantiate<T>(count)));
31+
32+
if (out.length != count) {
33+
out = changetype<T>(instantiate<T>(count));
34+
}
35+
36+
let index = 0;
37+
while (srcStart < srcEnd) {
38+
const code = load<u16>(srcStart);
39+
if (code - 48 <= 9 || code == 45) {
40+
const lastIndex = srcStart;
41+
srcStart += 2;
42+
43+
while (srcStart < srcEnd) {
44+
const code = load<u16>(srcStart);
45+
if (code == COMMA || code == BRACKET_RIGHT || isSpace(code)) {
46+
if (isFloat<valueof<T>>()) {
47+
unchecked((out[index++] = deserializeFloat<valueof<T>>(lastIndex, srcStart)));
48+
} else {
49+
unchecked((out[index++] = atoi<valueof<T>>(lastIndex, srcStart)));
50+
}
51+
break;
52+
}
53+
srcStart += 2;
54+
}
55+
}
56+
57+
srcStart += 2;
58+
}
59+
60+
return out;
61+
}
62+
63+
export function deserializeArrayBuffer(srcStart: usize, srcEnd: usize, dst: usize = 0): ArrayBuffer {
64+
const count = countTypedArrayElements(srcStart, srcEnd);
65+
let out = dst ? changetype<ArrayBuffer>(dst) : new ArrayBuffer(count);
66+
67+
if (out.byteLength != count) {
68+
out = new ArrayBuffer(count);
69+
}
70+
71+
const outStart = changetype<usize>(out);
72+
let index: usize = 0;
73+
74+
while (srcStart < srcEnd) {
75+
const code = load<u16>(srcStart);
76+
if (code - 48 <= 9 || code == 45) {
77+
const lastIndex = srcStart;
78+
srcStart += 2;
79+
80+
while (srcStart < srcEnd) {
81+
const code = load<u16>(srcStart);
82+
if (code == COMMA || code == BRACKET_RIGHT || isSpace(code)) {
83+
store<u8>(outStart + index++, atoi<u8>(lastIndex, srcStart));
84+
break;
85+
}
86+
srcStart += 2;
87+
}
88+
}
89+
90+
srcStart += 2;
91+
}
92+
93+
return out;
94+
}

0 commit comments

Comments
 (0)