Skip to content

Commit 27e23ae

Browse files
committed
fix: finish subtype-aware StaticArray deserialization
1 parent 94a1203 commit 27e23ae

4 files changed

Lines changed: 105 additions & 13 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: finish subtype-aware StaticArray deserialization
6+
- Reworked `StaticArray` deserialization to dispatch by element subtype, mirroring the existing `Array<subtype>` deserializer matrix instead of relying on a generic fallback.
7+
- 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.
8+
- Added regression coverage for richer `StaticArray` payloads and fixed a shared `JSON.Raw[]` delimiter edge case exposed by the new `StaticArray<JSON.Raw>` path.
9+
510
### fix: tighten default-path runtime correctness
611
- Fixed `JSON.Value` signed integer tagging and stringification so negative integer values no longer serialize as unsigned.
712
- Fixed `@omitif("...")` to use the same omit semantics as the callback form during transform-generated serialization.

assembly/__tests__/staticarray.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,48 @@ describe("Should round-trip static array objects with reordered fields", () => {
167167
expect(JSON.stringify(arr)).toBe('[{"x":1.0,"y":2.0,"z":3.0},{"x":4.0,"y":5.0,"z":6.0}]');
168168
});
169169

170+
describe("Should deserialize static arrays of JSON.Value", () => {
171+
const arr = JSON.parse<StaticArray<JSON.Value>>('[{"a":1},"x",false,null,[1,2]]');
172+
expect(arr.length).toBe(5);
173+
expect(arr[0].get<JSON.Obj>().get("a")!.toString()).toBe("1.0");
174+
expect(arr[1].get<string>()).toBe("x");
175+
expect(arr[2].get<bool>().toString()).toBe("false");
176+
expect(arr[3].type.toString()).toBe(JSON.Types.Null.toString());
177+
expect(JSON.stringify(arr[4].get<JSON.Value[]>())).toBe("[1.0,2.0]");
178+
expect(JSON.stringify(arr)).toBe('[{"a":1.0},"x",false,null,[1.0,2.0]]');
179+
});
180+
181+
describe("Should deserialize static arrays of maps and boxed values", () => {
182+
const maps = JSON.parse<StaticArray<Map<string, i32>>>('[{"a":1},{"b":2,"c":3}]');
183+
const boxed = JSON.parse<StaticArray<JSON.Box<i32>>>("[1,-2,3]");
184+
185+
expect(maps.length).toBe(2);
186+
expect(maps[0].get("a")).toBe(1);
187+
expect(maps[1].get("b")).toBe(2);
188+
expect(maps[1].get("c")).toBe(3);
189+
expect(JSON.stringify(maps)).toBe('[{"a":1},{"b":2,"c":3}]');
190+
191+
expect(boxed.length).toBe(3);
192+
expect(boxed[0].value).toBe(1);
193+
expect(boxed[1].value).toBe(-2);
194+
expect(boxed[2].value).toBe(3);
195+
expect(JSON.stringify(boxed)).toBe("[1,-2,3]");
196+
});
197+
198+
describe("Should preserve escaped nested strings inside static arrays", () => {
199+
const strings = JSON.parse<StaticArray<string>>('["path \\\\\\\\ and quote \\\\\\"","brackets [ ] { }"]');
200+
const raw = JSON.parse<StaticArray<JSON.Raw>>('["text with spaces","{\\"nested\\":[1,2,3]}"]');
201+
202+
expect(strings.length).toBe(2);
203+
expect(strings[0]).toBe('path \\\\ and quote \\"');
204+
expect(strings[1]).toBe("brackets [ ] { }");
205+
expect(JSON.stringify(strings)).toBe('["path \\\\\\\\ and quote \\\\\\"","brackets [ ] { }"]');
206+
207+
expect(raw.length).toBe(2);
208+
expect(raw[0].toString()).toBe('"text with spaces"');
209+
expect(raw[1].toString()).toBe('"{\\"nested\\":[1,2,3]}"');
210+
});
211+
170212
describe("Extended regression coverage - nested and escaped payloads", () => {
171213
expect(JSON.stringify(JSON.parse<i32>("0"))).toBe("0");
172214
expect(JSON.stringify(JSON.parse<bool>("true"))).toBe("true");

assembly/deserialize/simple/array/raw.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export function deserializeRawArray(srcStart: usize, srcEnd: usize, dst: usize):
1111
srcStart += 2;
1212
while (srcStart < srcEnd) {
1313
let code = load<u16>(srcStart);
14-
if (code == COMMA || isSpace(code)) {
14+
if (code == BRACKET_RIGHT) {
15+
break;
16+
} else if (code == COMMA || isSpace(code)) {
1517
srcStart += 2;
1618
continue;
1719
} else if (code == QUOTE) {
Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,71 @@
1-
import { deserializeStaticArrayInteger } from "./staticarray/integer";
2-
import { deserializeStaticArrayFloat } from "./staticarray/float";
1+
import { OBJECT, TOTAL_OVERHEAD } from "rt/common";
2+
import { JSON } from "../..";
3+
import { deserializeArbitraryArray } from "./array/arbitrary";
4+
import { deserializeArrayArray } from "./array/array";
5+
import { deserializeBooleanArray } from "./array/bool";
6+
import { deserializeBoxArray } from "./array/box";
7+
import { deserializeFloatArray } from "./array/float";
8+
import { deserializeIntegerArray } from "./array/integer";
9+
import { deserializeMapArray } from "./array/map";
10+
import { deserializeObjectArray } from "./array/object";
11+
import { deserializeRawArray } from "./array/raw";
12+
import { deserializeStructArray } from "./array/struct";
13+
import { deserializeStringArray } from "./array/string";
314
import { deserializeStaticArrayBoolean } from "./staticarray/bool";
15+
import { deserializeStaticArrayFloat } from "./staticarray/float";
16+
import { deserializeStaticArrayInteger } from "./staticarray/integer";
417
import { deserializeStaticArrayString } from "./staticarray/string";
5-
import { deserializeStaticArrayArray } from "./staticarray/array";
6-
import { deserializeStaticArrayStruct } from "./staticarray/struct";
18+
19+
@inline function materializeStaticArray<T extends StaticArray<any>>(src: valueof<T>[], dst: usize): T {
20+
const byteLength = (<usize>src.length) << alignof<valueof<T>>();
21+
let out = dst;
22+
23+
if (!out) {
24+
out = __new(byteLength, idof<T>());
25+
} else if (changetype<OBJECT>(out - TOTAL_OVERHEAD).rtSize != byteLength) {
26+
out = __renew(out, byteLength);
27+
}
28+
29+
const typed = changetype<T>(out);
30+
for (let i = 0; i < src.length; i++) {
31+
unchecked((typed[i] = unchecked(src[i])));
32+
}
33+
return typed;
34+
}
735

836
export function deserializeStaticArray<T extends StaticArray<any>>(srcStart: usize, srcEnd: usize, dst: usize): T {
937
if (isString<valueof<T>>()) {
10-
return <T>deserializeStaticArrayString(srcStart, srcEnd, dst);
38+
return changetype<T>(deserializeStaticArrayString(srcStart, srcEnd, dst));
1139
} else if (isBoolean<valueof<T>>()) {
1240
return deserializeStaticArrayBoolean<T>(srcStart, srcEnd, dst);
1341
} else if (isInteger<valueof<T>>()) {
1442
return deserializeStaticArrayInteger<T>(srcStart, srcEnd, dst);
1543
} else if (isFloat<valueof<T>>()) {
1644
return deserializeStaticArrayFloat<T>(srcStart, srcEnd, dst);
17-
} else if (isArrayLike<valueof<T>>()) {
18-
return deserializeStaticArrayArray<T>(srcStart, srcEnd, dst);
45+
} else if (isArray<valueof<T>>()) {
46+
return materializeStaticArray<T>(deserializeArrayArray<valueof<T>[]>(srcStart, srcEnd, 0), dst);
1947
} else if (isManaged<valueof<T>>() || isReference<valueof<T>>()) {
2048
const type = changetype<nonnull<valueof<T>>>(0);
21-
if (isDefined(type.__DESERIALIZE)) {
22-
return deserializeStaticArrayStruct<T>(srcStart, srcEnd, dst);
49+
if (type instanceof StaticArray) {
50+
return materializeStaticArray<T>(deserializeArrayArray<valueof<T>[]>(srcStart, srcEnd, 0), dst);
51+
} else if (type instanceof JSON.Value) {
52+
return materializeStaticArray<T>(changetype<valueof<T>[]>(deserializeArbitraryArray(srcStart, srcEnd, 0)), dst);
53+
} else if (type instanceof JSON.Box) {
54+
return materializeStaticArray<T>(changetype<valueof<T>[]>(deserializeBoxArray<valueof<T>[]>(srcStart, srcEnd, 0)), dst);
55+
} else if (type instanceof JSON.Obj) {
56+
return materializeStaticArray<T>(deserializeObjectArray<valueof<T>[]>(srcStart, srcEnd, 0), dst);
57+
} else if (type instanceof JSON.Raw) {
58+
return materializeStaticArray<T>(changetype<valueof<T>[]>(deserializeRawArray(srcStart, srcEnd, 0)), dst);
59+
} else if (type instanceof Map) {
60+
return materializeStaticArray<T>(deserializeMapArray<valueof<T>[]>(srcStart, srcEnd, 0), dst);
61+
// @ts-ignore: supplied by transform
62+
} else if (isDefined(type.__DESERIALIZE_CUSTOM)) {
63+
return materializeStaticArray<T>(deserializeStructArray<valueof<T>[]>(srcStart, srcEnd, 0), dst);
64+
// @ts-ignore: supplied by transform
65+
} else if (isDefined(type.__DESERIALIZE)) {
66+
return materializeStaticArray<T>(deserializeStructArray<valueof<T>[]>(srcStart, srcEnd, 0), dst);
2367
}
24-
throw new Error("Could not parse static array of type " + nameof<T>() + "!");
25-
} else {
26-
throw new Error("Could not parse static array of type " + nameof<T>() + "!");
2768
}
69+
70+
throw new Error("Could not parse static array of type " + nameof<T>() + "!");
2871
}

0 commit comments

Comments
 (0)