json-as 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +54 -29
  2. package/README.md +126 -33
  3. package/assembly/custom/chars.ts +39 -78
  4. package/assembly/deserialize/error.ts +21 -0
  5. package/assembly/deserialize/index/arbitrary.ts +50 -11
  6. package/assembly/deserialize/index/array.ts +2 -1
  7. package/assembly/deserialize/index/bool.ts +1 -1
  8. package/assembly/deserialize/index/float.ts +2 -4
  9. package/assembly/deserialize/index/integer.ts +2 -4
  10. package/assembly/deserialize/index/object.ts +9 -1
  11. package/assembly/deserialize/index/string.ts +39 -7
  12. package/assembly/deserialize/index/unsigned.ts +2 -4
  13. package/assembly/deserialize/index.ts +0 -1
  14. package/assembly/deserialize/naive/array/arbitrary.ts +3 -2
  15. package/assembly/deserialize/naive/array/array.ts +1 -0
  16. package/assembly/deserialize/naive/array/bool.ts +21 -11
  17. package/assembly/deserialize/naive/array/box.ts +13 -33
  18. package/assembly/deserialize/naive/array/float.ts +23 -13
  19. package/assembly/deserialize/naive/array/integer.ts +34 -22
  20. package/assembly/deserialize/naive/array/map.ts +1 -1
  21. package/assembly/deserialize/naive/array/object.ts +2 -1
  22. package/assembly/deserialize/naive/array/string.ts +29 -16
  23. package/assembly/deserialize/naive/array/struct.ts +19 -1
  24. package/assembly/deserialize/naive/bool.ts +18 -10
  25. package/assembly/deserialize/naive/date.ts +1 -2
  26. package/assembly/deserialize/naive/float.ts +3 -9
  27. package/assembly/deserialize/naive/integer.ts +1 -2
  28. package/assembly/deserialize/naive/map.ts +342 -43
  29. package/assembly/deserialize/naive/object.ts +250 -55
  30. package/assembly/deserialize/naive/raw.ts +10 -2
  31. package/assembly/deserialize/naive/set.ts +2 -5
  32. package/assembly/deserialize/naive/staticarray.ts +4 -4
  33. package/assembly/deserialize/naive/string.ts +46 -60
  34. package/assembly/deserialize/naive/unsigned.ts +1 -2
  35. package/assembly/deserialize/parseMode.ts +216 -0
  36. package/assembly/deserialize/simd/array/integer.ts +8 -10
  37. package/assembly/deserialize/simd/float.ts +38 -34
  38. package/assembly/deserialize/simd/integer.ts +2 -7
  39. package/assembly/deserialize/simd/string.ts +84 -39
  40. package/assembly/deserialize/string-validation.ts +34 -0
  41. package/assembly/deserialize/swar/array/arbitrary.ts +1 -2
  42. package/assembly/deserialize/swar/array/array.ts +64 -15
  43. package/assembly/deserialize/swar/array/bool.ts +15 -5
  44. package/assembly/deserialize/swar/array/box.ts +1 -2
  45. package/assembly/deserialize/swar/array/float.ts +399 -30
  46. package/assembly/deserialize/swar/array/generic.ts +13 -4
  47. package/assembly/deserialize/swar/array/integer.ts +29 -78
  48. package/assembly/deserialize/swar/array/map.ts +1 -2
  49. package/assembly/deserialize/swar/array/object.ts +12 -4
  50. package/assembly/deserialize/swar/array/raw.ts +1 -2
  51. package/assembly/deserialize/swar/array/shared.ts +7 -14
  52. package/assembly/deserialize/swar/array/string.ts +37 -13
  53. package/assembly/deserialize/swar/array/struct.ts +109 -21
  54. package/assembly/deserialize/swar/array.ts +1 -3
  55. package/assembly/deserialize/swar/float.ts +23 -21
  56. package/assembly/deserialize/swar/integer.ts +2 -7
  57. package/assembly/deserialize/swar/string.ts +81 -124
  58. package/assembly/deserialize/swar/typedarray.ts +4 -4
  59. package/assembly/index.d.ts +35 -24
  60. package/assembly/index.ts +1775 -281
  61. package/assembly/serialize/index/arbitrary.ts +70 -4
  62. package/assembly/serialize/index/bool.ts +1 -1
  63. package/assembly/serialize/index/float.ts +1 -5
  64. package/assembly/serialize/index/integer.ts +1 -1
  65. package/assembly/serialize/index/jsonarray.ts +51 -0
  66. package/assembly/serialize/index/object.ts +42 -3
  67. package/assembly/serialize/index/string.ts +1 -2
  68. package/assembly/serialize/index/typedarray.ts +1 -2
  69. package/assembly/serialize/index.ts +1 -0
  70. package/assembly/serialize/naive/array.ts +87 -34
  71. package/assembly/serialize/naive/bool.ts +0 -1
  72. package/assembly/serialize/naive/float.ts +15 -29
  73. package/assembly/serialize/naive/integer.ts +1 -5
  74. package/assembly/serialize/naive/map.ts +37 -0
  75. package/assembly/serialize/naive/raw.ts +1 -2
  76. package/assembly/serialize/naive/set.ts +0 -5
  77. package/assembly/serialize/naive/staticarray.ts +0 -6
  78. package/assembly/serialize/naive/string.ts +1 -10
  79. package/assembly/serialize/naive/typedarray.ts +0 -7
  80. package/assembly/serialize/simd/string.ts +1 -3
  81. package/assembly/serialize/swar/string.ts +1 -2
  82. package/assembly/util/atoi-fast.ts +4 -14
  83. package/assembly/util/bytes.ts +1 -2
  84. package/assembly/util/eisel-lemire.ts +179 -0
  85. package/assembly/util/idofd.ts +1 -2
  86. package/assembly/util/isSpace.ts +1 -2
  87. package/assembly/util/itoa-fast.ts +6 -9
  88. package/assembly/util/nextPowerOf2.ts +1 -2
  89. package/assembly/util/parsefloat-fast.ts +3 -5
  90. package/assembly/util/prettyWhitespaceSimd.ts +69 -0
  91. package/assembly/util/ptrToStr.ts +1 -2
  92. package/assembly/util/scanValueEndSimd.ts +83 -22
  93. package/assembly/util/scanValueEndSwar.ts +67 -25
  94. package/assembly/util/scientific.ts +5 -8
  95. package/assembly/util/snp.ts +1 -2
  96. package/assembly/util/swar-int.ts +5 -10
  97. package/assembly/util/swar.ts +2 -4
  98. package/assembly/util/validateJson.ts +338 -0
  99. package/lib/as-bs.ts +51 -51
  100. package/package.json +25 -8
  101. package/transform/lib/index.d.ts +6 -0
  102. package/transform/lib/index.js +874 -171
  103. package/transform/lib/types.d.ts +3 -1
  104. package/transform/lib/types.js +6 -0
  105. package/assembly/deserialize/index/struct.ts +0 -1
  106. package/assembly/deserialize/naive/struct.ts +0 -21
  107. package/assembly/util/dragonbox-cache.ts +0 -445
  108. package/assembly/util/dragonbox.ts +0 -652
package/assembly/index.ts CHANGED
@@ -10,12 +10,14 @@ import {
10
10
  serializeSet,
11
11
  serializeStaticArray,
12
12
  serializeBool,
13
+ serializeBoolUnsafe,
13
14
  serializeInteger,
14
- serializeFloat,
15
+ serializeIntegerUnsafe,
15
16
  serializeFloat32,
16
17
  serializeFloat64,
17
18
  serializeStruct,
18
19
  serializeObject,
20
+ serializeJsonArray,
19
21
  serializeRaw,
20
22
  serializeString,
21
23
  serializeArrayBufferUnsafe,
@@ -23,7 +25,7 @@ import {
23
25
  serializeTypedArray,
24
26
  } from "./serialize";
25
27
  import {
26
- deserializeBoolean,
28
+ deserializeBooleanCode,
27
29
  deserializeArray,
28
30
  deserializeFloat,
29
31
  deserializeMap,
@@ -34,11 +36,28 @@ import {
34
36
  deserializeStaticArray,
35
37
  deserializeArbitrary,
36
38
  deserializeObject,
39
+ deserializeJsonArray,
37
40
  deserializeRaw,
38
41
  deserializeString,
39
42
  deserializeArrayBuffer,
40
43
  deserializeTypedArray,
44
+ setParseSrc,
45
+ getParseSrc,
46
+ markProductionParseError,
47
+ takeProductionParseError,
48
+ failProductionParse as failProductionParseInternal,
41
49
  } from "./deserialize";
50
+ import {
51
+ beginStringFieldTrace,
52
+ beginObjectFieldTrace,
53
+ endStringFieldTrace,
54
+ isPrettyParse,
55
+ objectFieldTraceMask,
56
+ objectFieldTraceSeparator,
57
+ objectFieldTraceTier,
58
+ recordObjectFieldTrace,
59
+ setPrettyParse,
60
+ } from "./deserialize/parseMode";
42
61
  import {
43
62
  BACK_SLASH,
44
63
  BRACE_LEFT,
@@ -53,81 +72,167 @@ import {
53
72
  FALSE_WORD_U64,
54
73
  } from "./custom/chars";
55
74
  import { itoa_buffered } from "util/number";
56
- import {
57
- dragonbox_f32_buffered,
58
- dragonbox_f64_buffered,
59
- } from "./util/dragonbox";
75
+ import { dtoa_buffered, ftoa_buffered } from "xjb-as";
60
76
  import { ptrToStr } from "./util/ptrToStr";
61
77
  import { atoi, bytes, scanStringEnd } from "./util";
62
78
  import { scanValueEnd_SIMD } from "./util/scanValueEndSimd";
63
79
  import { scanValueEnd_SWAR } from "./util/scanValueEndSwar";
64
-
65
- // --- NaN-boxing encoding for JSON.Value ----------------------------------
66
- // JSON.Value packs its type tag and payload into a single 8-byte word.
67
- // Real f64 values are stored as their raw IEEE-754 bits; every other type is
68
- // encoded inside a quiet-NaN ("boxed") word: a 5-bit tag (the JSON.Types id,
69
- // or Struct for @json classes) plus a 45-bit payload holding a 32-bit pointer
70
- // or a small scalar. 64-bit ints that don't fit the payload are spilled to a
71
- // heap StaticArray<u64> referenced by the payload pointer and flagged in the
72
- // sign bit. Only quiet-NaN doubles whose top two mantissa bits are both set
73
- // collide with the box signature; hardware NaN is 0x7FF8.. so finite/Inf/NaN
74
- // doubles all pass through untouched.
75
- //
76
- // The managed reference packed into the word is traced by JSON.Value.__visit;
77
- // AssemblyScript only wires that hook for library-declared classes, so the
78
- // json-as transform marks this source as a library source (see afterParse).
79
- // @ts-expect-error: Decorator valid here
80
- @inline const VAL_QNAN: u64 = 0x7ffc000000000000; // boxed signature (quiet NaN)
81
- // @ts-expect-error: Decorator valid here
82
- @inline const VAL_TAG_SHIFT: u8 = 45;
83
- // @ts-expect-error: Decorator valid here
84
- @inline const VAL_PAYLOAD_MASK: u64 = 0x00001fffffffffff; // low 45 bits
85
- // @ts-expect-error: Decorator valid here
86
- @inline const VAL_PTR_MASK: u64 = 0xffffffff; // wasm32 pointer
87
- // @ts-expect-error: Decorator valid here
88
- @inline const VAL_BOX64: u64 = 0x8000000000000000; // sign bit: 64-bit int spilled to heap
89
- // @ts-expect-error: Decorator valid here
90
- @inline const VAL_NULL: u64 = VAL_QNAN; // tag 0 (Null), payload 0
91
- // @ts-expect-error: Decorator valid here
92
- @inline const VAL_I64_LIMIT: i64 = 17592186044416; // 2^44 inline range is [-2^44, 2^44)
93
- // @ts-expect-error: Decorator valid here
94
- @inline const VAL_U64_LIMIT: u64 = 35184372088832; // 2^45 inline range is [0, 2^45)
95
-
96
- // @ts-expect-error: Decorator valid here
97
- @inline function valBoxed(w: u64): bool {
80
+ import {
81
+ hasLongPrettyIndent_SIMD,
82
+ skipPrettyWhitespace_SIMD,
83
+ } from "./util/prettyWhitespaceSimd";
84
+ import { normalizeJSONEncoding, validateJSON } from "./util/validateJson";
85
+
86
+ const VAL_QNAN: u64 = 0x7ffc000000000000; // boxed signature (quiet NaN)
87
+ const VAL_TAG_SHIFT: u8 = 45;
88
+ const VAL_PAYLOAD_MASK: u64 = 0x00001fffffffffff; // low 45 bits
89
+ const VAL_PTR_MASK: u64 = 0xffffffff; // wasm32 pointer
90
+ const VAL_BOX64: u64 = 0x8000000000000000; // sign bit: 64-bit int spilled to heap
91
+ const VAL_NULL: u64 = VAL_QNAN; // tag 0 (Null), payload 0
92
+ const VAL_I64_LIMIT: i64 = 17592186044416; // 2^44 - inline range is [-2^44, 2^44)
93
+ const VAL_U64_LIMIT: u64 = 35184372088832; // 2^45 - inline range is [0, 2^45)
94
+
95
+ // Lazy value-slot payload layout (45-bit box payload), see JSON.Value.lazyBits.
96
+ // Compact form (bit 44 = 0): a source-relative start offset (23 bits) + the
97
+ // value's length (21 bits), both in UTF-16 units. Offset-heavy on purpose:
98
+ // object/array fields are usually small while the document can be large, so
99
+ // offset overflow (a field late in a big doc) is the realistic trigger, not a
100
+ // single giant field - so the offset field gets the wider range. bit 44 flags
101
+ // the absolute (scan-on-demand) fallback for a source/value past those ranges.
102
+ // bits [0..22] offset (<=~16MB src) · [23..43] length (<=~4MB val) · 44 abs
103
+ const LZ_OFF_BITS: u64 = 23;
104
+ const LZ_OFF_MASK: u64 = 0x7fffff; // (1 << 23) - 1
105
+ const LZ_LEN_MASK: u64 = 0x1fffff; // (1 << 21) - 1
106
+ const LZ_ABS_FLAG: u64 = 0x100000000000; // 1 << 44
107
+
108
+ // A materialized String box only uses the low 32 payload bits for its pointer,
109
+ // so two spare bits [32..33] cache the value's serialize-escape class. This lets
110
+ // re-serializing a dynamic string skip the per-char escape scan - a clean string
111
+ // emits via a single memcpy. `valPtr` masks bit 32+ off, so the pointer (and GC)
112
+ // are unaffected.
113
+ // 0 = unclassified · 1 = clean (no escaping -> memcpy) · 2 = needs escaping
114
+ const VAL_STR_CLASS_SHIFT: u64 = 32;
115
+ const VAL_STR_CLASS_MASK: u64 = 0x0000000300000000; // bits [32..33]
116
+ const STR_CLASS_UNKNOWN: u32 = 0;
117
+ const STR_CLASS_CLEAN: u32 = 1;
118
+ const STR_CLASS_ESCAPE: u32 = 2;
119
+
120
+ // One rooted immutable source/type pair is enough to make repeated parsing of
121
+ // an exact generated-default document allocation-only after its first match.
122
+ // The source reference prevents pointer reuse after GC; a different source or
123
+ // target type simply takes the normal matcher and replaces this cache on hit.
124
+ let LAST_DEFAULT_SOURCE: string | null = null;
125
+ let LAST_DEFAULT_TYPE: u32 = 0;
126
+
127
+ function valBoxed(w: u64): bool {
98
128
  return (w & VAL_QNAN) == VAL_QNAN;
99
129
  }
100
- // @ts-expect-error: Decorator valid here
101
- @inline function valTag(w: u64): u32 {
130
+ function valTag(w: u64): u32 {
102
131
  return <u32>((w >> VAL_TAG_SHIFT) & 0x1f);
103
132
  }
104
- // @ts-expect-error: Decorator valid here
105
- @inline function valPayload(w: u64): u64 {
133
+ function valPayload(w: u64): u64 {
106
134
  return w & VAL_PAYLOAD_MASK;
107
135
  }
108
- // @ts-expect-error: Decorator valid here
109
- @inline function valPtr(w: u64): usize {
136
+ function valPtr(w: u64): usize {
110
137
  return <usize>(w & VAL_PTR_MASK);
111
138
  }
112
- // @ts-expect-error: Decorator valid here
113
- @inline function valBox(tag: u32, payload: u64): u64 {
139
+ function valBox(tag: u32, payload: u64): u64 {
114
140
  return (
115
141
  VAL_QNAN | ((<u64>tag) << VAL_TAG_SHIFT) | (payload & VAL_PAYLOAD_MASK)
116
142
  );
117
143
  }
118
- // @ts-expect-error: Decorator valid here
119
- @inline function valIntTag<T>(): u32 {
144
+ function valLazy(w: u64): bool {
145
+ return valBoxed(w) && valTag(w) == JSON.Types.Lazy;
146
+ }
147
+ function valIntTag<T>(): u32 {
120
148
  if (sizeof<T>() == 1) return isSigned<T>() ? JSON.Types.I8 : JSON.Types.U8;
121
149
  if (sizeof<T>() == 2) return isSigned<T>() ? JSON.Types.I16 : JSON.Types.U16;
122
150
  if (sizeof<T>() == 4) return isSigned<T>() ? JSON.Types.I32 : JSON.Types.U32;
123
151
  return isSigned<T>() ? JSON.Types.I64 : JSON.Types.U64;
124
152
  }
125
153
 
154
+ function hashUtf16(ptr: usize, len: i32): u32 {
155
+ let h: u32 = 2166136261;
156
+ for (let i = 0; i < len; i++) {
157
+ h ^= <u32>load<u16>(ptr + ((<usize>i) << 1));
158
+ h *= 16777619;
159
+ }
160
+ h ^= <u32>len;
161
+ h *= 16777619;
162
+ return h;
163
+ }
164
+
165
+ // v128 path: compare 8 code units (16 bytes) per step, then a u64 step (4) and
166
+ // a scalar tail. Each load is bounded by `len`, so it never reads past either
167
+ // key. Only reachable (and thus only compiled) when the SIMD feature is on, so
168
+ // the intrinsics don't break the naive/swar builds.
169
+ function utf16Equals_SIMD(ptrA: usize, ptrB: usize, len: i32): bool {
170
+ let i = 0;
171
+ for (; i + 8 <= len; i += 8) {
172
+ const off = (<usize>i) << 1;
173
+ if (v128.any_true(v128.xor(v128.load(ptrA + off), v128.load(ptrB + off))))
174
+ return false;
175
+ }
176
+ for (; i + 4 <= len; i += 4) {
177
+ const off = (<usize>i) << 1;
178
+ if (load<u64>(ptrA + off) != load<u64>(ptrB + off)) return false;
179
+ }
180
+ for (; i < len; i++) {
181
+ const off = (<usize>i) << 1;
182
+ if (load<u16>(ptrA + off) != load<u16>(ptrB + off)) return false;
183
+ }
184
+ return true;
185
+ }
186
+
187
+ function utf16Equals(ptrA: usize, ptrB: usize, len: i32): bool {
188
+ if (ASC_FEATURE_SIMD) return utf16Equals_SIMD(ptrA, ptrB, len);
189
+ // Scalar: 4 code units (one u64) per step, scalar tail. Bounded by `len`.
190
+ let i = 0;
191
+ for (; i + 4 <= len; i += 4) {
192
+ const off = (<usize>i) << 1;
193
+ if (load<u64>(ptrA + off) != load<u64>(ptrB + off)) return false;
194
+ }
195
+ for (; i < len; i++) {
196
+ const off = (<usize>i) << 1;
197
+ if (load<u16>(ptrA + off) != load<u16>(ptrB + off)) return false;
198
+ }
199
+ return true;
200
+ }
201
+
126
202
  // Shared zero-length sentinel for JSON.Obj's key buffer, so an empty object
127
203
  // allocates no key storage until its first key is inserted. Never mutated.
128
204
  // @ts-expect-error: Decorator valid here
129
205
  @lazy const EMPTY_KEYS: StaticArray<u16> = new StaticArray<u16>(0);
130
206
 
207
+ // Shared zero-length sentinel for JSON.Obj's key-position/index buffers.
208
+ // Never mutated.
209
+ // @ts-expect-error: Decorator valid here
210
+ @lazy const EMPTY_I32S: StaticArray<i32> = new StaticArray<i32>(0);
211
+
212
+ // Shared zero-length sentinel for JSON.Obj's value-slot buffer, so an empty
213
+ // object allocates no slot storage until its first value. Never mutated.
214
+ // @ts-expect-error: Decorator valid here
215
+ @lazy const EMPTY_VALS: StaticArray<u64> = new StaticArray<u64>(0);
216
+
217
+ // A JSON.Obj with at most this many keys resolves lookups by linear scan and
218
+ // never allocates/hashes a key index. Most JSON objects are small and dynamic
219
+ // access touches only a few keys, so the O(n) index build a hash table needs is
220
+ // pure overhead below this size. Above it, the hash index amortizes.
221
+ const OBJ_LINEAR_MAX: i32 = 6;
222
+
223
+ // Deferred-value record for a lazy JSON.Value (see JSON.Types.Lazy). `lz` packs
224
+ // the unparsed source slice as (sliceStart << 32) | sliceEnd - the same encoding
225
+ // the transform uses for @lazy struct fields. `src` is the GC anchor that keeps
226
+ // the source string (and therefore the slice pointers, which index into its
227
+ // UTF-16 buffer) alive until the value is materialized. Managed so `src` is
228
+ // traced automatically; a JSON.Value's __visit traces the LazyRef itself.
229
+ // @ts-expect-error: decorators allowed here
230
+ @final
231
+ class LazyRef {
232
+ lz: u64 = 0;
233
+ src: string = "";
234
+ }
235
+
131
236
  export namespace JSON {
132
237
  /**
133
238
  * On-demand field marker. `JSON.Lazy<T>` is structurally just `T` (a no-op
@@ -139,15 +244,14 @@ export namespace JSON {
139
244
  export type Lazy<T> = T;
140
245
 
141
246
  /**
142
- * Whether a lazy slot's value is JSON null for `@omitnull` on lazy fields,
247
+ * Whether a lazy slot's value is JSON null - for `@omitnull` on lazy fields,
143
248
  * without forcing materialization. The slot encodes the state: `u64.MAX_VALUE`
144
249
  * = materialized (null iff the value pointer is 0), `0` = absent (null), any
145
250
  * other value = a not-yet-parsed slice range (null iff it is literally `null`).
146
251
  * @param valPtr pointer of the materialized value (0 when null)
147
252
  * @param lz the packed slot
148
253
  */
149
- // @ts-expect-error: inline
150
- @inline export function __lazyIsNull(valPtr: usize, lz: u64): bool {
254
+ export function __lazyIsNull(valPtr: usize, lz: u64): bool {
151
255
  if (lz == u64.MAX_VALUE) return valPtr == 0;
152
256
  if (lz == 0) return true;
153
257
  const hi = <usize>(lz >>> 32);
@@ -182,11 +286,7 @@ export namespace JSON {
182
286
  * @param data T
183
287
  * @returns string
184
288
  */
185
- // @ts-expect-error: inline
186
- @inline export function stringify<T>(
187
- data: T,
188
- out: string | null = null,
189
- ): string {
289
+ export function stringify<T>(data: T, out: string | null = null): string {
190
290
  if (isBoolean<T>()) {
191
291
  if (out) {
192
292
  if (<bool>data == true) {
@@ -226,13 +326,14 @@ export namespace JSON {
226
326
  return data.toString();
227
327
  } else if (isFloat<T>(data)) {
228
328
  out = out
229
- ? changetype<string>(__renew(changetype<usize>(out), 64))
230
- : changetype<string>(__new(64, idof<string>()));
329
+ ? changetype<string>(__renew(changetype<usize>(out), 128))
330
+ : changetype<string>(__new(128, idof<string>()));
331
+ const startPtr = changetype<usize>(out);
231
332
  const bytes =
232
333
  (sizeof<T>() == 4
233
- ? dragonbox_f32_buffered(changetype<usize>(out), <f32>data)
234
- : dragonbox_f64_buffered(changetype<usize>(out), <f64>data)) << 1;
235
- return changetype<string>(__renew(changetype<usize>(out), bytes));
334
+ ? ftoa_buffered(startPtr, <f32>data)
335
+ : dtoa_buffered(startPtr, <f64>data)) << 1;
336
+ return changetype<string>(__renew(startPtr, bytes));
236
337
  } else if (isNullable<T>() && changetype<usize>(data) == <usize>0) {
237
338
  if (out) {
238
339
  out = changetype<string>(__renew(changetype<usize>(out), 8));
@@ -289,27 +390,138 @@ export namespace JSON {
289
390
  // A type-correct "zero" for any T: null pointer for references, 0/false for
290
391
  // value types. `changetype<T>(0)` alone fails for bool/f64 (size mismatch),
291
392
  // so branch on isReference at compile time.
292
- // @ts-ignore: inline
293
- @inline function __zero<T>(): T {
393
+ function __zero<T>(): T {
294
394
  // @ts-ignore: compile-time intrinsic
295
395
  if (isReference<T>() || isManaged<T>()) return changetype<T>(0);
296
396
  return <T>0;
297
397
  }
298
398
 
299
- // @ts-expect-error: inline
300
- @inline export function parse<T>(data: string, out: T = __zero<T>()): T {
399
+ export function parse<T>(data: string, out: T = __zero<T>()): T {
400
+ if (JSON_STRICT) {
401
+ data = normalizeJSONEncoding(data);
402
+ if (!validateJSON(data)) throw new Error("Invalid JSON syntax");
403
+ }
404
+ let managePretty = false;
405
+ let simdPretty = false;
406
+ if (isReference<T>() || isManaged<T>()) {
407
+ const type = changetype<nonnull<T>>(0);
408
+ // @ts-expect-error: marker supplied by the json-as transform
409
+ if (isDefined(type.__DESERIALIZE_PRETTY_SPECIALIZED)) {
410
+ managePretty = true;
411
+ if (
412
+ ASC_FEATURE_SIMD &&
413
+ data.length >= 16_384 &&
414
+ data.length <= 2_000_000
415
+ ) {
416
+ const start = changetype<usize>(data);
417
+ const end = start + ((<usize>data.length) << 1);
418
+ let valueStart = start;
419
+ while (valueStart < end && JSON.Util.isSpace(load<u16>(valueStart)))
420
+ valueStart += 2;
421
+ if (valueStart + 2 < end) {
422
+ const open = load<u16>(valueStart);
423
+ const next = load<u16>(valueStart, 2);
424
+ simdPretty =
425
+ (open == BRACE_LEFT || open == BRACKET_LEFT) &&
426
+ (next == 10 || next == 13) &&
427
+ hasLongPrettyIndent_SIMD(valueStart, end);
428
+ }
429
+ }
430
+ }
431
+ }
432
+ // Resolve an exact generated-default hit directly at the public boundary.
433
+ // This avoids entering the general parser and avoids polling the cold error
434
+ // flag for a source that has already been fully compared. Generated matcher
435
+ // methods do not read `this`, so the compile-time type sentinel is a safe
436
+ // receiver until a match tells us an object allocation is actually needed.
437
+ if ((isReference<T>() || isManaged<T>()) && changetype<usize>(out) == 0) {
438
+ const type = changetype<nonnull<T>>(0);
439
+ // @ts-expect-error: marker supplied by the json-as transform
440
+ if (isDefined(type.__DESERIALIZE_DEFAULT)) {
441
+ const start = changetype<usize>(data);
442
+ const typeId = idof<nonnull<T>>();
443
+ const knownMatch =
444
+ LAST_DEFAULT_TYPE == typeId &&
445
+ changetype<usize>(LAST_DEFAULT_SOURCE) == start;
446
+ if (knownMatch) {
447
+ // @ts-expect-error: paired generated allocation-only factory
448
+ return changetype<T>(type.__DESERIALIZE_DEFAULT_NEW());
449
+ }
450
+ const end = start + ((<usize>data.length) << 1);
451
+ // @ts-expect-error: generated method is a receiver-independent factory
452
+ const value = type.__DESERIALIZE_DEFAULT(start, end);
453
+ if (changetype<usize>(value) != 0) {
454
+ LAST_DEFAULT_SOURCE = data;
455
+ LAST_DEFAULT_TYPE = typeId;
456
+ return changetype<T>(value);
457
+ }
458
+ }
459
+ }
460
+ // Generated scalar-only schemas cannot create lazy JSON.Obj/Arr/Value
461
+ // slices. Avoid two global reference writes (and their barriers) for these
462
+ // very small, latency-sensitive parses.
463
+ if (isReference<T>() || isManaged<T>()) {
464
+ const type = changetype<nonnull<T>>(0);
465
+ // @ts-expect-error: marker supplied by the json-as transform
466
+ if (isDefined(type.__DESERIALIZE_SOURCE_FREE)) {
467
+ let result: T;
468
+ beginStringFieldTrace(data, changetype<usize>(out), idof<nonnull<T>>());
469
+ if (managePretty) {
470
+ const prevPretty = isPrettyParse();
471
+ setPrettyParse(simdPretty);
472
+ result = parseInternal<T>(data, out);
473
+ setPrettyParse(prevPretty);
474
+ } else {
475
+ result = parseInternal<T>(data, out);
476
+ }
477
+ const failed = takeProductionParseError();
478
+ endStringFieldTrace(!failed);
479
+ if (failed) throw new Error("Incomplete or malformed JSON value");
480
+ return result;
481
+ }
482
+ }
483
+ // Anchor the source for any lazy JSON.Obj/JSON.Value built while parsing, so
484
+ // their stored slice pointers (into `data`'s buffer) stay valid and resolve
485
+ // against the right string. Save/restore makes nested parses (e.g. a custom
486
+ // deserializer calling JSON.parse, or JSON.Obj.from) re-entrant-safe.
487
+ const prevSrc = getParseSrc();
488
+ setParseSrc(data);
489
+ if (isReference<T>() || isManaged<T>()) {
490
+ beginStringFieldTrace(data, changetype<usize>(out), idof<nonnull<T>>());
491
+ } else {
492
+ beginStringFieldTrace(data, 0, 0);
493
+ }
494
+ let result: T;
495
+ if (managePretty) {
496
+ const prevPretty = isPrettyParse();
497
+ setPrettyParse(simdPretty);
498
+ result = parseInternal<T>(data, out);
499
+ setPrettyParse(prevPretty);
500
+ } else {
501
+ result = parseInternal<T>(data, out);
502
+ }
503
+ setParseSrc(prevSrc);
504
+ const failed = takeProductionParseError();
505
+ endStringFieldTrace(!failed);
506
+ if (failed) throw new Error("Incomplete or unterminated JSON value");
507
+ return result;
508
+ }
509
+
510
+ function parseInternal<T>(data: string, out: T = __zero<T>()): T {
301
511
  let dataPtr = changetype<usize>(data);
302
512
  const dataEnd = dataPtr + bytes(data);
303
513
  // Entry point skips leading whitespace: every deserialize handler may then
304
514
  // assume srcStart points at the first non-whitespace char. Handlers must
305
515
  // NOT re-skip leading whitespace themselves. (Trailing whitespace is left
306
- // intact scalars stop at the value end, composites self-trim, and
516
+ // intact - scalars stop at the value end, composites self-trim, and
307
517
  // JSON.Raw intentionally preserves trailing bytes.)
308
518
  while (dataPtr < dataEnd && JSON.Util.isSpace(load<u16>(dataPtr)))
309
519
  dataPtr += 2;
310
520
  const dataSize = dataEnd - dataPtr;
311
521
  if (isBoolean<T>()) {
312
- return deserializeBoolean(dataPtr, dataPtr + dataSize) as T;
522
+ const code = deserializeBooleanCode(dataPtr, dataPtr + dataSize);
523
+ if (code == 0) markProductionParseError();
524
+ return (code == 2) as T;
313
525
  } else if (isInteger<T>()) {
314
526
  return isSigned<T>()
315
527
  ? deserializeInteger<T>(dataPtr, dataPtr + dataSize)
@@ -323,7 +535,15 @@ export namespace JSON {
323
535
  ) {
324
536
  return null;
325
537
  } else if (isString<T>()) {
326
- return deserializeString(dataPtr, dataPtr + dataSize) as T;
538
+ // Whole-string decoders require quote-inclusive bounds. Trim only the
539
+ // insignificant suffix here; the normal no-whitespace path pays one
540
+ // predicted-not-taken branch.
541
+ let stringEnd = dataEnd;
542
+ while (stringEnd > dataPtr && JSON.Util.isSpace(load<u16>(stringEnd - 2)))
543
+ stringEnd -= 2;
544
+ const value = deserializeString(dataPtr, stringEnd);
545
+ if (changetype<usize>(value) == 0) markProductionParseError();
546
+ return value as T;
327
547
  } else {
328
548
  let type: nonnull<T> = changetype<nonnull<T>>(0);
329
549
  // @ts-expect-error: Defined by transform
@@ -337,19 +557,45 @@ export namespace JSON {
337
557
  isDefined(type.__DESERIALIZE_FAST)
338
558
  ) {
339
559
  // Reuse the caller-supplied `out` graph when given; otherwise allocate.
340
- const obj = changetype<usize>(out)
560
+ const reuse = changetype<usize>(out) != 0;
561
+ const obj = reuse
341
562
  ? changetype<nonnull<T>>(changetype<usize>(out))
342
563
  : changetype<nonnull<T>>(
343
564
  __new(offsetof<nonnull<T>>(), idof<nonnull<T>>()),
344
565
  );
566
+ // A successful generated full-write path assigns every field. Zeroing a
567
+ // fresh payload is enough to make its reference slots safe while
568
+ // avoiding redundant default graph construction; a fast miss still
569
+ // initializes normally before the slow retry below.
570
+ // @ts-expect-error: Defined by transform
571
+ const fullWrite = isDefined(type.__DESERIALIZE_FULL_WRITE);
572
+ if (!reuse) {
573
+ if (fullWrite) {
574
+ memory.fill(changetype<usize>(obj), 0, offsetof<nonnull<T>>());
575
+ } else if (isDefined(type.__INITIALIZE)) {
576
+ // @ts-expect-error: Defined by transform
577
+ obj.__INITIALIZE();
578
+ }
579
+ }
345
580
  // @ts-expect-error: Defined by transform
346
581
  if (isDefined(type.__DESERIALIZE_FAST)) {
347
- // @ts-expect-error: Defined by transform
348
- const fastEnd = obj.__DESERIALIZE_FAST(
349
- dataPtr,
350
- dataPtr + dataSize,
351
- obj,
352
- );
582
+ let fastEnd: usize;
583
+ // @ts-expect-error: Defined by transform in SIMD builds
584
+ if (
585
+ ASC_FEATURE_SIMD &&
586
+ isPrettyParse() &&
587
+ isDefined(type.__DESERIALIZE_FAST_PRETTY)
588
+ ) {
589
+ // @ts-expect-error: Defined by transform
590
+ fastEnd = obj.__DESERIALIZE_FAST_PRETTY(
591
+ dataPtr,
592
+ dataPtr + dataSize,
593
+ obj,
594
+ );
595
+ } else {
596
+ // @ts-expect-error: Defined by transform
597
+ fastEnd = obj.__DESERIALIZE_FAST(dataPtr, dataPtr + dataSize, obj);
598
+ }
353
599
  // A non-zero return means the fast path matched; accept it when only
354
600
  // trailing whitespace remains (pretty-printed input ends with a
355
601
  // newline, so the cursor stops just past `}` rather than at srcEnd).
@@ -358,7 +604,7 @@ export namespace JSON {
358
604
  JSON.Util.skipWhitespace(fastEnd, dataPtr + dataSize) ==
359
605
  dataPtr + dataSize
360
606
  ) {
361
- // @ts-expect-error: Defined by transform for @lazy-field structs
607
+ // @ts-expect-error: Defined by transform for @lazy-field structs -
362
608
  // pins the source so stored slice ranges stay valid.
363
609
  if (isDefined(obj.__SET_SRC)) obj.__SET_SRC(data);
364
610
  return obj;
@@ -368,7 +614,12 @@ export namespace JSON {
368
614
  // @ts-expect-error: Defined by transform
369
615
  if (isDefined(type.__DESERIALIZE_SLOW)) {
370
616
  // @ts-expect-error: Defined by transform
371
- obj.__DESERIALIZE_SLOW(dataPtr, dataPtr + dataSize, obj);
617
+ const slowEnd = obj.__DESERIALIZE_SLOW(
618
+ dataPtr,
619
+ dataPtr + dataSize,
620
+ obj,
621
+ );
622
+ if (slowEnd == 0) markProductionParseError();
372
623
  // @ts-expect-error: Defined by transform for @lazy-field structs.
373
624
  if (isDefined(obj.__SET_SRC)) obj.__SET_SRC(data);
374
625
  return obj;
@@ -383,12 +634,18 @@ export namespace JSON {
383
634
  0,
384
635
  );
385
636
  } else if (type instanceof Array) {
637
+ // Reuse the caller-supplied array when given (no allocation); the
638
+ // element loop overwrites slots and trims length. Otherwise allocate.
386
639
  // @ts-expect-error
387
- return deserializeArray<nonnull<T>>(
640
+ const value = deserializeArray<nonnull<T>>(
388
641
  dataPtr,
389
642
  dataPtr + dataSize,
390
- changetype<usize>(instantiate<T>()),
643
+ changetype<usize>(out) != 0
644
+ ? changetype<usize>(out)
645
+ : changetype<usize>(instantiate<T>()),
391
646
  );
647
+ if (changetype<usize>(value) == 0) markProductionParseError();
648
+ return value;
392
649
  } else if (
393
650
  type instanceof Int8Array ||
394
651
  type instanceof Uint8Array ||
@@ -413,8 +670,15 @@ export namespace JSON {
413
670
  // @ts-expect-error
414
671
  return deserializeSet<nonnull<T>>(dataPtr, dataPtr + dataSize, 0);
415
672
  } else if (type instanceof Map) {
673
+ // Reuse the caller-supplied map when given (keys overwrite in place).
416
674
  // @ts-expect-error
417
- return deserializeMap<nonnull<T>>(dataPtr, dataPtr + dataSize, 0);
675
+ const value = deserializeMap<nonnull<T>>(
676
+ dataPtr,
677
+ dataPtr + dataSize,
678
+ changetype<usize>(out),
679
+ );
680
+ if (changetype<usize>(value) == 0) markProductionParseError();
681
+ return value;
418
682
  } else if (type instanceof Date) {
419
683
  // @ts-expect-error
420
684
  return deserializeDate(dataPtr, dataPtr + dataSize);
@@ -422,11 +686,36 @@ export namespace JSON {
422
686
  // @ts-expect-error: type
423
687
  return deserializeRaw(dataPtr, dataPtr + dataSize);
424
688
  } else if (type instanceof JSON.Value) {
689
+ // Reuse the caller-supplied JSON.Value handle when given (`out`); the
690
+ // deserializer writes the parsed bits into it. Otherwise allocate.
425
691
  // @ts-expect-error
426
- return deserializeArbitrary(dataPtr, dataPtr + dataSize, 0);
692
+ const value = deserializeArbitrary(
693
+ dataPtr,
694
+ dataPtr + dataSize,
695
+ changetype<usize>(out),
696
+ );
697
+ if (changetype<usize>(value) == 0) markProductionParseError();
698
+ return value;
427
699
  } else if (type instanceof JSON.Obj) {
700
+ // Reuse the caller-supplied JSON.Obj (cleared, buffers kept). Otherwise allocate.
701
+ // @ts-expect-error
702
+ const value = deserializeObject(
703
+ dataPtr,
704
+ dataPtr + dataSize,
705
+ changetype<usize>(out),
706
+ );
707
+ if (changetype<usize>(value) == 0) markProductionParseError();
708
+ return value;
709
+ } else if (type instanceof JSON.Arr) {
710
+ // Reuse the caller-supplied JSON.Arr (cleared, buffers kept). Otherwise allocate.
428
711
  // @ts-expect-error
429
- return deserializeObject(dataPtr, dataPtr + dataSize, 0);
712
+ const value = deserializeJsonArray(
713
+ dataPtr,
714
+ dataPtr + dataSize,
715
+ changetype<usize>(out),
716
+ );
717
+ if (changetype<usize>(value) == 0) markProductionParseError();
718
+ return value;
430
719
  } else if (type instanceof JSON.Box) {
431
720
  // @ts-expect-error
432
721
  return new JSON.Box(parseBox(data, changetype<nonnull<T>>(0).value));
@@ -453,47 +742,33 @@ export namespace JSON {
453
742
  */
454
743
  export namespace Types {
455
744
  /** Represents a null value */
456
- // @ts-expect-error
457
- @inline export const Null: u16 = 0;
458
- // @ts-expect-error
459
- @inline export const Raw: u16 = 1;
460
- // @ts-expect-error
461
- @inline export const U8: u16 = 2;
462
- // @ts-expect-error
463
- @inline export const U16: u16 = 3;
464
- // @ts-expect-error
465
- @inline export const U32: u16 = 4;
466
- // @ts-expect-error
467
- @inline export const U64: u16 = 5;
468
- // @ts-expect-error
469
- @inline export const I8: u16 = 6;
470
- // @ts-expect-error
471
- @inline export const I16: u16 = 7;
472
- // @ts-expect-error
473
- @inline export const I32: u16 = 8;
474
- // @ts-expect-error
475
- @inline export const I64: u16 = 9;
476
- // @ts-expect-error
477
- @inline export const F32: u16 = 10;
478
- // @ts-expect-error
479
- @inline export const F64: u16 = 11;
480
- // @ts-expect-error
481
- @inline export const Bool: u16 = 12;
745
+ export const Null: u16 = 0;
746
+ export const Raw: u16 = 1;
747
+ export const U8: u16 = 2;
748
+ export const U16: u16 = 3;
749
+ export const U32: u16 = 4;
750
+ export const U64: u16 = 5;
751
+ export const I8: u16 = 6;
752
+ export const I16: u16 = 7;
753
+ export const I32: u16 = 8;
754
+ export const I64: u16 = 9;
755
+ export const F32: u16 = 10;
756
+ export const F64: u16 = 11;
757
+ export const Bool: u16 = 12;
482
758
  // Managed
483
- // @ts-expect-error
484
- @inline export const String: u16 = 13;
485
- // @ts-expect-error
486
- @inline export const Object: u16 = 14;
487
- // @ts-expect-error
488
- @inline export const Array: u16 = 15;
489
- // @ts-expect-error
490
- @inline export const Map: u16 = 16;
491
- // @ts-expect-error
492
- @inline export const Struct: u16 = 17;
493
- // @ts-expect-error
494
- @inline export const TypedArray: u16 = 18;
495
- // @ts-expect-error
496
- @inline export const ArrayBuffer: u16 = 19;
759
+ export const String: u16 = 13;
760
+ export const Object: u16 = 14;
761
+ export const Array: u16 = 15;
762
+ export const Map: u16 = 16;
763
+ export const Struct: u16 = 17;
764
+ export const TypedArray: u16 = 18;
765
+ export const ArrayBuffer: u16 = 19;
766
+ /**
767
+ * Internal: a not-yet-materialized value holding a raw source slice
768
+ * (see LazyRef). Never returned by `JSON.Value.type` - accessing the value
769
+ * materializes it first, so callers only ever observe the concrete type.
770
+ */
771
+ export const Lazy: u16 = 20;
497
772
  }
498
773
 
499
774
  /**
@@ -542,7 +817,7 @@ export namespace JSON {
542
817
  * @param data - A valid JSON string
543
818
  * @returns A new Raw instance
544
819
  */
545
- @inline static from(data: string): JSON.Raw {
820
+ static from(data: string): JSON.Raw {
546
821
  return new JSON.Raw(data);
547
822
  }
548
823
  }
@@ -566,8 +841,7 @@ export namespace JSON {
566
841
  * ```
567
842
  */
568
843
  // @ts-expect-error: decorators allowed here
569
- @final
570
- export class Value {
844
+ @final export class Value {
571
845
  /** Map of struct type IDs to their serialization function indices */
572
846
  @lazy static METHODS: Map<u32, u32> = new Map<u32, u32>();
573
847
 
@@ -584,6 +858,7 @@ export namespace JSON {
584
858
  * stored object's runtime header.
585
859
  */
586
860
  get type(): u16 {
861
+ this.materialize();
587
862
  const w = this.bits;
588
863
  if (!valBoxed(w)) return JSON.Types.F64;
589
864
  const tag = valTag(w);
@@ -598,7 +873,7 @@ export namespace JSON {
598
873
  * Creates an JSON.Value instance with no set value.
599
874
  * @returns An instance of JSON.Value.
600
875
  */
601
- @inline static empty(): JSON.Value {
876
+ static empty(): JSON.Value {
602
877
  const out = changetype<JSON.Value>(
603
878
  __new(offsetof<JSON.Value>(), idof<JSON.Value>()),
604
879
  );
@@ -611,7 +886,7 @@ export namespace JSON {
611
886
  * @param value - The value to be encapsulated.
612
887
  * @returns An instance of JSON.Value.
613
888
  */
614
- @inline static from<T>(value: T): JSON.Value {
889
+ static from<T>(value: T): JSON.Value {
615
890
  if (value instanceof JSON.Value) return value;
616
891
  const out = changetype<JSON.Value>(
617
892
  __new(offsetof<JSON.Value>(), idof<JSON.Value>()),
@@ -620,12 +895,248 @@ export namespace JSON {
620
895
  return out;
621
896
  }
622
897
 
898
+ /**
899
+ * Creates a lazy JSON.Value wrapping the unparsed source slice
900
+ * `[sliceStart, sliceEnd)` (UTF-16 byte pointers into `src`). The slice is
901
+ * parsed into a concrete value on first access (see `materialize`); until
902
+ * then it serializes by passing those bytes through verbatim. `src` anchors
903
+ * the source string so the slice pointers stay valid. Internal - produced by
904
+ * the dynamic deserializers, never called by user code.
905
+ */
906
+ static fromSlice(
907
+ sliceStart: usize,
908
+ sliceEnd: usize,
909
+ src: string,
910
+ ): JSON.Value {
911
+ const ref = new LazyRef();
912
+ ref.lz = ((<u64>sliceStart) << 32) | (<u64>(<u32>sliceEnd));
913
+ ref.src = src;
914
+ const out = changetype<JSON.Value>(
915
+ __new(offsetof<JSON.Value>(), idof<JSON.Value>()),
916
+ );
917
+ out.bits = valBox(JSON.Types.Lazy, <u64>changetype<usize>(ref));
918
+ return out;
919
+ }
920
+
921
+ /**
922
+ * Copy `src`'s NaN-boxed bits into the already-allocated value at `dst`,
923
+ * applying the itcms write barrier for any managed payload (mirrors
924
+ * `__visit`'s tracing rule: tags >= String carry a pointer, as do
925
+ * heap-spilled 64-bit ints). Lets `JSON.parse<JSON.Value>(data, out)` reuse
926
+ * the caller's handle without materializing `src`.
927
+ */
928
+ @unsafe static __adoptInto(dst: usize, src: JSON.Value): JSON.Value {
929
+ const target = changetype<JSON.Value>(dst);
930
+ const bits = src.bits;
931
+ target.bits = bits;
932
+ if (valBoxed(bits)) {
933
+ const tag = valTag(bits);
934
+ if (
935
+ tag >= JSON.Types.String ||
936
+ ((tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
937
+ (bits & VAL_BOX64) != 0)
938
+ ) {
939
+ __link(dst, valPtr(bits), false);
940
+ }
941
+ }
942
+ return target;
943
+ }
944
+
945
+ /**
946
+ * Parses a deferred (lazy) value into a concrete one in place, replacing the
947
+ * boxed slice with the real boxed value (cached for subsequent reads). The
948
+ * deferred shapes are strings, objects and arrays; a string materializes to
949
+ * a `string`, while a composite's own nested deferred children stay lazy -
950
+ * one level is peeled per access. A no-op for already-materialized values.
951
+ * Never allocates during GC (not called from `__visit`).
952
+ */
953
+ private materialize(): void {
954
+ const w = this.bits;
955
+ if (!valLazy(w)) return;
956
+ const ref = changetype<LazyRef>(valPtr(w));
957
+ const lz = ref.lz;
958
+ this.bits = JSON.Value.parseSliceBits(
959
+ <usize>(lz >>> 32),
960
+ <usize>(<u32>lz),
961
+ ref.src,
962
+ );
963
+ }
964
+
965
+ /**
966
+ * Parses the raw slice `[start, end)` (the allocating shapes only: string,
967
+ * object, array) into a concrete value and returns its NaN-boxed bits. A
968
+ * composite's own nested string/composite children stay lazy - one level is
969
+ * peeled. Shared by `JSON.Value.materialize` (standalone lazy values) and
970
+ * `JSON.Obj`'s value-slot materialization.
971
+ */
972
+ static parseSliceBits(start: usize, end: usize, src: string): u64 {
973
+ const first = load<u16>(start);
974
+ if (first == 0x22 /* '"' */) {
975
+ // A string anchors no children, so no source pinning is needed.
976
+ return valBox(
977
+ JSON.Types.String,
978
+ <u64>changetype<usize>(deserializeString(start, end)),
979
+ );
980
+ }
981
+ // Pin the same source for the one level we peel so its children defer too.
982
+ const prev = getParseSrc();
983
+ setParseSrc(src);
984
+ let bits: u64;
985
+ if (first == 0x7b /* '{' */) {
986
+ bits = valBox(
987
+ JSON.Types.Object,
988
+ <u64>changetype<usize>(deserializeObject(start, end, 0)),
989
+ );
990
+ } else {
991
+ bits = valBox(
992
+ JSON.Types.Array,
993
+ <u64>changetype<usize>(deserializeJsonArray(start, end, 0)),
994
+ );
995
+ }
996
+ setParseSrc(prev);
997
+ return bits;
998
+ }
999
+
1000
+ /**
1001
+ * Internal: the packed slice `(start << 32) | end` if this value is still a
1002
+ * deferred slice, else 0. Lets the serializer pass raw bytes through without
1003
+ * forcing materialization. `start` is a non-zero pointer, so a real slice is
1004
+ * never 0.
1005
+ */
1006
+ __lazySlice(): u64 {
1007
+ const w = this.bits;
1008
+ if (!valLazy(w)) return 0;
1009
+ return changetype<LazyRef>(valPtr(w)).lz;
1010
+ }
1011
+
1012
+ /**
1013
+ * The cached serialize-escape class of a materialized String value: 0 = not
1014
+ * yet classified, 1 = clean (no chars need escaping, so it serializes via a
1015
+ * single memcpy), 2 = needs escaping. Stored in two spare payload bits so the
1016
+ * scan is paid once and reused. Only meaningful for String-tagged values.
1017
+ */
1018
+ __strClass(): u32 {
1019
+ return <u32>((this.bits >> VAL_STR_CLASS_SHIFT) & 3);
1020
+ }
1021
+ /** Records the serialize-escape class on this String value (see __strClass). */
1022
+ __setStrClass(c: u32): void {
1023
+ this.bits =
1024
+ (this.bits & ~VAL_STR_CLASS_MASK) | ((<u64>c) << VAL_STR_CLASS_SHIFT);
1025
+ }
1026
+ /**
1027
+ * Raw boxed bits. Lets the JSON.Obj/JSON.Arr serializers read back a class
1028
+ * the serializer cached on a transient value (see `serializeArbitrary`) and
1029
+ * persist it into their flat u64 slot, so re-serializing reuses it.
1030
+ */
1031
+ __bits(): u64 {
1032
+ return this.bits;
1033
+ }
1034
+
1035
+ // --- value-slot helpers (JSON.Obj stores values as flat NaN-boxed u64 ---
1036
+ // slots instead of heap JSON.Value objects; these build/inspect/decode the
1037
+ // raw bits without allocating, while keeping the box layout encapsulated).
1038
+
1039
+ /**
1040
+ * Bits for a deferred slot. The 45-bit payload has two forms (bit 44 selects):
1041
+ *
1042
+ * compact (bit 44 = 0): the value's start *offset* and *length*, both in
1043
+ * UTF-16 units relative to the source base - `(length << 22) | offset`.
1044
+ * Gives the exact end with no scan for any value inside a source up to
1045
+ * 2^22 units (8 MB) whose own length is also < 8 MB. This is the common
1046
+ * case, and storing a relative offset (vs an absolute pointer) is also
1047
+ * GC-relocation-safe.
1048
+ *
1049
+ * absolute (bit 44 = 1): the absolute start pointer in the low 32 bits;
1050
+ * the end is scanned on demand. Fallback for a source or value past the
1051
+ * 8 MB field range - rare, and correct (it scans from the value start;
1052
+ * scanning a composite cannot safely resume mid-value).
1053
+ */
1054
+ static lazyBits(srcBase: usize, start: usize, end: usize): u64 {
1055
+ const offset = <u64>((start - srcBase) >> 1);
1056
+ const length = <u64>((end - start) >> 1);
1057
+ let payload: u64;
1058
+ if (offset <= LZ_OFF_MASK && length <= LZ_LEN_MASK) {
1059
+ payload = (length << LZ_OFF_BITS) | offset;
1060
+ } else {
1061
+ payload = LZ_ABS_FLAG | ((<u64>start) & VAL_PTR_MASK);
1062
+ }
1063
+ return valBox(JSON.Types.Lazy, payload);
1064
+ }
1065
+ /** The value-end pointer of a lazy slot - from the packed length, or scanned. */
1066
+ static slotEnd(w: u64, srcBase: usize, srcEnd: usize): usize {
1067
+ const p = valPayload(w);
1068
+ if (p & LZ_ABS_FLAG) {
1069
+ return JSON.Util.scanValueEnd<JSON.Value>(
1070
+ <usize>(p & VAL_PTR_MASK),
1071
+ srcEnd,
1072
+ );
1073
+ }
1074
+ const start = srcBase + ((<usize>(p & LZ_OFF_MASK)) << 1);
1075
+ const length = <usize>((p >> LZ_OFF_BITS) & LZ_LEN_MASK);
1076
+ return start + (length << 1);
1077
+ }
1078
+ /** Bits for a JSON null. */
1079
+ static nullBits(): u64 {
1080
+ return VAL_NULL;
1081
+ }
1082
+ /** Bits for a boolean. */
1083
+ static boolBits(b: bool): u64 {
1084
+ return valBox(JSON.Types.Bool, b ? 1 : 0);
1085
+ }
1086
+ /** Bits for an f64 (raw IEEE-754, NaN canonicalized off the box signature). */
1087
+ static f64Bits(v: f64): u64 {
1088
+ return isNaN(v) ? 0x7ff8000000000000 : reinterpret<u64>(v);
1089
+ }
1090
+ /** Whether a slot is still a deferred (start-pointer) slice. */
1091
+ static slotIsLazy(w: u64): bool {
1092
+ return valLazy(w);
1093
+ }
1094
+ /** The start pointer held by a lazy slot (compact offset or absolute). */
1095
+ static slotPtr(w: u64, srcBase: usize): usize {
1096
+ const p = valPayload(w);
1097
+ if (p & LZ_ABS_FLAG) return <usize>(p & VAL_PTR_MASK);
1098
+ return srcBase + ((<usize>(p & LZ_OFF_MASK)) << 1);
1099
+ }
1100
+ /** Wraps raw bits in a JSON.Value (eager scalar / materialized reference). */
1101
+ static fromBits(w: u64): JSON.Value {
1102
+ const out = changetype<JSON.Value>(
1103
+ __new(offsetof<JSON.Value>(), idof<JSON.Value>()),
1104
+ );
1105
+ out.bits = w;
1106
+ return out;
1107
+ }
1108
+ /** Concrete NaN-boxed bits for a value of type T (materializing if lazy). */
1109
+ static bitsFrom<T>(value: T): u64 {
1110
+ const v = JSON.Value.from<T>(value);
1111
+ v.materialize();
1112
+ return v.bits;
1113
+ }
1114
+ /** Decodes NaN-boxed bits into T (the body of the instance `get<T>`). */
1115
+ static decodeBits<T>(w: u64): T {
1116
+ if (isFloat<T>()) {
1117
+ if (sizeof<T>() == 4) return <T>reinterpret<f32>(<u32>valPayload(w));
1118
+ return <T>reinterpret<f64>(w);
1119
+ } else if (isInteger<T>()) {
1120
+ if (sizeof<T>() == 8) {
1121
+ if (w & VAL_BOX64) return load<T>(valPtr(w));
1122
+ if (isSigned<T>()) return <T>((<i64>(valPayload(w) << 19)) >> 19);
1123
+ return <T>valPayload(w);
1124
+ }
1125
+ return <T>valPayload(w);
1126
+ } else if (isBoolean<T>()) {
1127
+ return <T>valPayload(w);
1128
+ } else if (isReference<T>()) {
1129
+ return changetype<T>(valPtr(w));
1130
+ }
1131
+ return unreachable();
1132
+ }
1133
+
623
1134
  /**
624
1135
  * Gets the type of a given value as a JSON.Types enum.
625
1136
  * @param value - any
626
1137
  * @returns JSON.Types
627
1138
  */
628
- @inline getType<T>(value: T): JSON.Types {
1139
+ getType<T>(value: T): JSON.Types {
629
1140
  if (isNullable<T>() && changetype<usize>(value) === 0)
630
1141
  return JSON.Types.Null;
631
1142
  if (isBoolean<T>()) return JSON.Types.Bool;
@@ -672,13 +1183,14 @@ export namespace JSON {
672
1183
  if (value instanceof Map) return JSON.Types.Map;
673
1184
  if (value instanceof JSON.Raw) return JSON.Types.Raw;
674
1185
  if (value instanceof JSON.Obj) return JSON.Types.Object;
1186
+ if (value instanceof JSON.Arr) return JSON.Types.Array;
675
1187
  return JSON.Types.Null;
676
1188
  }
677
1189
  /**
678
1190
  * Sets the value of the JSON.Value instance.
679
1191
  * @param value - The value to be set.
680
1192
  */
681
- @inline set<T>(value: T): void {
1193
+ set<T>(value: T): void {
682
1194
  if (value instanceof JSON.Box) {
683
1195
  this.set(value.value);
684
1196
  } else if (isBoolean<T>()) {
@@ -739,9 +1251,16 @@ export namespace JSON {
739
1251
  this.bits = valBox(JSON.Types.Map, <u64>changetype<usize>(value));
740
1252
  } else if (value instanceof JSON.Obj) {
741
1253
  this.bits = valBox(JSON.Types.Object, <u64>changetype<usize>(value));
1254
+ } else if (value instanceof JSON.Arr) {
1255
+ this.bits = valBox(JSON.Types.Array, <u64>changetype<usize>(value));
742
1256
  // @ts-expect-error
743
1257
  } else if (isArray<T>() && idof<valueof<T>>() == idof<JSON.Value>()) {
744
- this.bits = valBox(JSON.Types.Array, <u64>changetype<usize>(value));
1258
+ // A JSON.Value[] is converted to the buffer-backed JSON.Arr form, so
1259
+ // the Array tag always boxes a JSON.Arr.
1260
+ this.bits = valBox(
1261
+ JSON.Types.Array,
1262
+ <u64>changetype<usize>(JSON.Arr.from<T>(value)),
1263
+ );
745
1264
  }
746
1265
  }
747
1266
 
@@ -774,24 +1293,9 @@ export namespace JSON {
774
1293
  * Gets the value of the JSON.Value instance.
775
1294
  * @returns The encapsulated value.
776
1295
  */
777
- @inline get<T>(): T {
778
- const w = this.bits;
779
- if (isFloat<T>()) {
780
- if (sizeof<T>() == 4) return <T>reinterpret<f32>(<u32>valPayload(w));
781
- return <T>reinterpret<f64>(w);
782
- } else if (isInteger<T>()) {
783
- if (sizeof<T>() == 8) {
784
- if (w & VAL_BOX64) return load<T>(valPtr(w));
785
- if (isSigned<T>()) return <T>((<i64>(valPayload(w) << 19)) >> 19);
786
- return <T>valPayload(w);
787
- }
788
- return <T>valPayload(w);
789
- } else if (isBoolean<T>()) {
790
- return <T>valPayload(w);
791
- } else if (isReference<T>()) {
792
- return changetype<T>(valPtr(w));
793
- }
794
- return unreachable();
1296
+ get<T>(): T {
1297
+ this.materialize();
1298
+ return JSON.Value.decodeBits<T>(this.bits);
795
1299
  }
796
1300
 
797
1301
  /**
@@ -799,7 +1303,7 @@ export namespace JSON {
799
1303
  * Alias for .get<T>()
800
1304
  * @returns The encapsulated value.
801
1305
  */
802
- @inline as<T>(): T {
1306
+ as<T>(): T {
803
1307
  return this.get<T>();
804
1308
  }
805
1309
 
@@ -808,7 +1312,8 @@ export namespace JSON {
808
1312
  * Alias for .get<T>()
809
1313
  * @returns The encapsulated value.
810
1314
  */
811
- @inline asBox<T>(): Box<T> | null {
1315
+ asBox<T>(): Box<T> | null {
1316
+ this.materialize();
812
1317
  if (this.type === JSON.Types.Null) return null;
813
1318
  return changetype<Box<T>>(JSON.Box.fromValue<T>(this));
814
1319
  }
@@ -818,6 +1323,7 @@ export namespace JSON {
818
1323
  * @returns The string representation of the JSON.Value.
819
1324
  */
820
1325
  toString(): string {
1326
+ this.materialize();
821
1327
  switch (this.type) {
822
1328
  case JSON.Types.Null:
823
1329
  return "null";
@@ -849,19 +1355,7 @@ export namespace JSON {
849
1355
  return this.get<JSON.Raw>().toString();
850
1356
  }
851
1357
  case JSON.Types.Array: {
852
- const arr = this.get<JSON.Value[]>();
853
- if (!arr.length) return "[]";
854
- let out = "[";
855
- const end = arr.length - 1;
856
- for (let i = 0; i < end; i++) {
857
- const element = unchecked(arr[i]);
858
- out += element.toString() + ",";
859
- }
860
-
861
- const element = unchecked(arr[end]);
862
- out += element.toString() + "]";
863
-
864
- return out.toString();
1358
+ return JSON.stringify(this.get<JSON.Arr>());
865
1359
  }
866
1360
  case JSON.Types.TypedArray:
867
1361
  case JSON.Types.ArrayBuffer: {
@@ -885,6 +1379,14 @@ export namespace JSON {
885
1379
  const w = this.bits;
886
1380
  if (!valBoxed(w)) return; // raw f64 holds no reference
887
1381
  const tag = valTag(w);
1382
+ // A deferred value carries a LazyRef pointer; tracing it keeps the LazyRef
1383
+ // (and, transitively, its `src` anchor) alive. Must precede the
1384
+ // `tag >= String` branch since Lazy(20) would otherwise fall into it.
1385
+ // Trace-only - never materialize here (no allocation during GC).
1386
+ if (tag == JSON.Types.Lazy) {
1387
+ __visit(valPtr(w), cookie);
1388
+ return;
1389
+ }
888
1390
  // String(13)..ArrayBuffer(19) and Struct all carry a managed pointer;
889
1391
  // Raw(1) is intentionally not traced (matches prior behavior).
890
1392
  if (tag >= JSON.Types.String) {
@@ -917,25 +1419,50 @@ export namespace JSON {
917
1419
  * console.log(JSON.stringify(obj)); // {"key":"value","count":42}
918
1420
  * ```
919
1421
  */
920
- export class Obj {
921
- // Keys are packed into one growable buffer, each prefixed by a u16 length
922
- // (UTF-16 code units), instead of allocating a heap string per key. Values
923
- // are a parallel array. A key -> position index is built lazily on the
924
- // first keyed access (never during parsing). Per-object allocation count
925
- // matches the previous Map-based storage, while deserialization avoids the
926
- // per-key string allocation and hashing entirely.
1422
+ @final export class Obj {
927
1423
  _kbuf: StaticArray<u16> = EMPTY_KEYS;
928
1424
  _kused: i32 = 0;
929
- _vals: JSON.Value[] = [];
930
- private _index: Map<string, i32> | null = null;
1425
+ _kpos: StaticArray<i32> = EMPTY_I32S;
1426
+ _vals: StaticArray<u64> = EMPTY_VALS;
1427
+ _vused: i32 = 0;
1428
+ /** Source string the lazy slot pointers index into; anchors it for GC. */
1429
+ _src: string = "";
1430
+ /** Untouched source range for whole-object serialization passthrough. */
1431
+ _rawStart: usize = 0;
1432
+ _rawEnd: usize = 0;
1433
+ private _index: StaticArray<i32> | null = null;
1434
+ private _indexMask: i32 = 0;
931
1435
 
932
1436
  constructor() {}
933
1437
 
1438
+ /**
1439
+ * Pre-sizes the three flat parse buffers from the source span. The parser
1440
+ * still grows normally if an unusual key-heavy document exceeds the
1441
+ * estimate; ordinary API-shaped objects avoid the repeated 8→16→32...
1442
+ * allocation/copy ladder.
1443
+ */
1444
+ reserveForParse(sourceUnits: usize): void {
1445
+ let keyNeed = <i32>((sourceUnits + 2) / 3);
1446
+ if (keyNeed < 16) keyNeed = 16;
1447
+ let keyCap = 16;
1448
+ while (keyCap < keyNeed) keyCap <<= 1;
1449
+ if (this._kbuf.length < keyCap) this._kbuf = new StaticArray<u16>(keyCap);
1450
+
1451
+ let slotNeed = <i32>(sourceUnits >> 6);
1452
+ if (slotNeed < 8) slotNeed = 8;
1453
+ let slotCap = 8;
1454
+ while (slotCap < slotNeed) slotCap <<= 1;
1455
+ if (this._kpos.length < slotCap)
1456
+ this._kpos = new StaticArray<i32>(slotCap);
1457
+ if (this._vals.length < slotCap)
1458
+ this._vals = new StaticArray<u64>(slotCap);
1459
+ }
1460
+
934
1461
  /**
935
1462
  * Gets the number of key-value pairs in the object.
936
1463
  */
937
- @inline get size(): i32 {
938
- return this._vals.length;
1464
+ get size(): i32 {
1465
+ return this._vused;
939
1466
  }
940
1467
 
941
1468
  /** Grows the key buffer to hold at least `need` code units. */
@@ -954,11 +1481,28 @@ export namespace JSON {
954
1481
  this._kbuf = nb;
955
1482
  }
956
1483
 
1484
+ /** Grows the key-position buffer to hold at least `need` entries. */
1485
+ private ensureKeyPosCap(need: i32): void {
1486
+ const cap = this._kpos.length;
1487
+ if (cap >= need) return;
1488
+ let n = cap ? cap : 8;
1489
+ while (n < need) n <<= 1;
1490
+ const nb = new StaticArray<i32>(n);
1491
+ if (this._vused)
1492
+ memory.copy(
1493
+ changetype<usize>(nb),
1494
+ changetype<usize>(this._kpos),
1495
+ (<usize>this._vused) << 2,
1496
+ );
1497
+ this._kpos = nb;
1498
+ }
1499
+
957
1500
  /** Appends a length-prefixed key (from a source memory range). */
958
- private pushKeyBytes(keyStart: usize, keyEnd: usize): void {
1501
+ private pushKeyBytes(keyStart: usize, keyEnd: usize, slotIndex: i32): void {
959
1502
  const len = <i32>((keyEnd - keyStart) >> 1);
960
1503
  const pos = this._kused;
961
1504
  this.ensureKeyCap(pos + 1 + len);
1505
+ this.ensureKeyPosCap(slotIndex + 1);
962
1506
  const buf = changetype<usize>(this._kbuf);
963
1507
  store<u16>(buf + ((<usize>pos) << 1), <u16>len);
964
1508
  if (len)
@@ -967,6 +1511,7 @@ export namespace JSON {
967
1511
  keyStart,
968
1512
  (<usize>len) << 1,
969
1513
  );
1514
+ unchecked((this._kpos[slotIndex] = pos));
970
1515
  this._kused = pos + 1 + len;
971
1516
  }
972
1517
 
@@ -982,69 +1527,320 @@ export namespace JSON {
982
1527
  return out;
983
1528
  }
984
1529
 
985
- /**
986
- * Appends a key (from a source memory range) and value without a
987
- * duplicate-key check. Used by the deserializer — no per-key string
988
- * allocation, no hashing.
989
- */
990
- appendRaw<T>(keyStart: usize, keyEnd: usize, value: T): void {
991
- this.pushKeyBytes(keyStart, keyEnd);
992
- this._vals.push(JSON.Value.from<T>(value));
993
- const idx = this._index;
994
- if (idx !== null) {
995
- const len = <i32>((keyEnd - keyStart) >> 1);
996
- const k = changetype<string>(__new((<usize>len) << 1, idof<string>()));
997
- if (len) memory.copy(changetype<usize>(k), keyStart, (<usize>len) << 1);
998
- idx.set(k, this._vals.length - 1);
999
- }
1530
+ /** Grows the value-slot buffer to hold at least `need` slots. */
1531
+ private ensureValCap(need: i32): void {
1532
+ const cap = this._vals.length;
1533
+ if (cap >= need) return;
1534
+ let n = cap ? cap : 8;
1535
+ while (n < need) n <<= 1;
1536
+ const nb = new StaticArray<u64>(n);
1537
+ if (this._vused)
1538
+ memory.copy(
1539
+ changetype<usize>(nb),
1540
+ changetype<usize>(this._vals),
1541
+ (<usize>this._vused) << 3,
1542
+ );
1543
+ this._vals = nb;
1000
1544
  }
1001
1545
 
1002
- /** Builds (once) and returns the lazy key -> position index. */
1003
- private buildIndex(): Map<string, i32> {
1004
- let idx = this._index;
1005
- if (idx === null) {
1006
- idx = new Map<string, i32>();
1007
- const buf = changetype<usize>(this._kbuf);
1008
- const used = this._kused;
1009
- let pos = 0;
1010
- let i = 0;
1011
- while (pos < used) {
1012
- const len = <i32>load<u16>(buf + ((<usize>pos) << 1));
1013
- idx.set(this.makeKey(pos + 1, len), i++);
1014
- pos += 1 + len;
1546
+ /** Writes a slot and, if it carries a managed pointer, links it for the GC. */
1547
+ private storeSlot(i: i32, bits: u64): void {
1548
+ unchecked((this._vals[i] = bits));
1549
+ if (valBoxed(bits)) {
1550
+ const tag = valTag(bits);
1551
+ // Lazy slots hold an interior `_src` pointer (anchored by the _src
1552
+ // field), not an owned object - never link those.
1553
+ if (
1554
+ tag != JSON.Types.Lazy &&
1555
+ (tag >= JSON.Types.String ||
1556
+ ((tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
1557
+ bits & VAL_BOX64))
1558
+ ) {
1559
+ __link(changetype<usize>(this), valPtr(bits), false);
1015
1560
  }
1016
- this._index = idx;
1017
1561
  }
1018
- return idx;
1019
1562
  }
1020
1563
 
1021
- /**
1022
- * Sets a key-value pair in the object, overwriting any existing value.
1023
- * @param key - The string key
1024
- * @param value - The value (will be wrapped in JSON.Value)
1025
- */
1026
- set<T>(key: string, value: T): void {
1027
- const idx = this.buildIndex();
1028
- if (idx.has(key)) {
1029
- unchecked((this._vals[idx.get(key)] = JSON.Value.from<T>(value)));
1564
+ /** Appends a value slot (raw NaN-boxed bits). */
1565
+ private pushValSlot(bits: u64): void {
1566
+ const pos = this._vused;
1567
+ this.ensureValCap(pos + 1);
1568
+ this._vused = pos + 1;
1569
+ this.storeSlot(pos, bits);
1570
+ }
1571
+
1572
+ /** End pointer of the source buffer (upper bound for scanning a lazy slot). */
1573
+ private srcEnd(): usize {
1574
+ return changetype<usize>(this._src) + ((<usize>this._src.length) << 1);
1575
+ }
1576
+
1577
+ /** Compares a lookup key against the stored key bytes for slot `i`. */
1578
+ private keyEquals(i: i32, key: string): bool {
1579
+ const pos = unchecked(this._kpos[i]);
1580
+ const buf = changetype<usize>(this._kbuf) + ((<usize>pos) << 1);
1581
+ const len = <i32>load<u16>(buf);
1582
+ if (len != <i32>key.length) return false;
1583
+ return utf16Equals(changetype<usize>(key), buf + 2, len);
1584
+ }
1585
+
1586
+ /** Compares two stored key slots without materializing strings. */
1587
+ private slotEqualsSlot(a: i32, b: i32): bool {
1588
+ const posa = unchecked(this._kpos[a]);
1589
+ const bufA = changetype<usize>(this._kbuf) + ((<usize>posa) << 1);
1590
+ const lenA = <i32>load<u16>(bufA);
1591
+ const posb = unchecked(this._kpos[b]);
1592
+ const bufB = changetype<usize>(this._kbuf) + ((<usize>posb) << 1);
1593
+ const lenB = <i32>load<u16>(bufB);
1594
+ if (lenA != lenB) return false;
1595
+ return utf16Equals(bufA + 2, bufB + 2, lenA);
1596
+ }
1597
+
1598
+ /** Hashes the stored key for slot `i`. */
1599
+ private keyHashAt(i: i32): u32 {
1600
+ const pos = unchecked(this._kpos[i]);
1601
+ const buf = changetype<usize>(this._kbuf) + ((<usize>pos) << 1);
1602
+ const len = <i32>load<u16>(buf);
1603
+ return hashUtf16(buf + 2, len);
1604
+ }
1605
+
1606
+ /** Resolves a key to its slot index, or -1 if absent. */
1607
+ private indexOf(key: string): i32 {
1608
+ const n = this._vused;
1609
+ const keyLen = <i32>key.length;
1610
+ // Small objects: scan linearly and never build a hash index. The keys are
1611
+ // packed contiguously in _kbuf and the length prefix rejects mismatches
1612
+ // before any byte compare. Scan from the end so a duplicate key resolves
1613
+ // to its LAST occurrence - JSON last-value-wins, matching buildIndex()
1614
+ // (which overwrites the slot on a collision) for objects above the
1615
+ // threshold; a forward scan would return the first and disagree.
1616
+ if (n <= OBJ_LINEAR_MAX) {
1617
+ const kbuf = changetype<usize>(this._kbuf);
1618
+ const keyPtr = changetype<usize>(key);
1619
+ const kpos = this._kpos;
1620
+ for (let i = n - 1; i >= 0; i--) {
1621
+ const buf = kbuf + ((<usize>unchecked(kpos[i])) << 1);
1622
+ if (
1623
+ <i32>load<u16>(buf) == keyLen &&
1624
+ utf16Equals(keyPtr, buf + 2, keyLen)
1625
+ )
1626
+ return i;
1627
+ }
1628
+ return -1;
1629
+ }
1630
+ const idx = this.buildIndex();
1631
+ const mask = this._indexMask;
1632
+ if (mask == 0) {
1633
+ const entry = unchecked(idx[0]);
1634
+ return entry != 0 && this.keyEquals(entry - 1, key) ? entry - 1 : -1;
1635
+ }
1636
+ let slot = <i32>(
1637
+ (hashUtf16(changetype<usize>(key), <i32>key.length) & (<u32>mask))
1638
+ );
1639
+ const start = slot;
1640
+ while (true) {
1641
+ const entry = unchecked(idx[slot]);
1642
+ if (entry == 0) return -1;
1643
+ const i = entry - 1;
1644
+ if (this.keyEquals(i, key)) return i;
1645
+ slot = (slot + 1) & mask;
1646
+ // A correctly-maintained table stays below full load, so an empty slot
1647
+ // is found first. This wrap check is a safety net against spinning
1648
+ // forever should that invariant ever be violated.
1649
+ if (slot == start) return -1;
1650
+ }
1651
+ }
1652
+
1653
+ /** Parses a lazy slot in place, caching the concrete box, and returns it. */
1654
+ private materializeSlot(i: i32): u64 {
1655
+ const slot = unchecked(this._vals[i]);
1656
+ if (!JSON.Value.slotIsLazy(slot)) return slot;
1657
+ // A materialized nested object can subsequently be mutated through the
1658
+ // returned reference. Stop whole-object passthrough so serialization
1659
+ // observes that mutation and walks the cached slots instead.
1660
+ this._rawStart = 0;
1661
+ this._rawEnd = 0;
1662
+ const base = changetype<usize>(this._src);
1663
+ const start = JSON.Value.slotPtr(slot, base);
1664
+ const end = JSON.Value.slotEnd(slot, base, this.srcEnd());
1665
+ const bits = JSON.Value.parseSliceBits(start, end, this._src);
1666
+ this.storeSlot(i, bits);
1667
+ return bits;
1668
+ }
1669
+
1670
+ /**
1671
+ * Appends a key (from a source memory range) and a precomputed NaN-boxed
1672
+ * value slot without a duplicate-key check. Used by the deserializer - no
1673
+ * per-key string allocation, no per-value object, no hashing.
1674
+ */
1675
+ appendRawSlot(keyStart: usize, keyEnd: usize, bits: u64): void {
1676
+ this._rawStart = 0;
1677
+ this._rawEnd = 0;
1678
+ const slotIndex = this._vused;
1679
+ this.pushKeyBytes(keyStart, keyEnd, slotIndex);
1680
+ this.pushValSlot(bits);
1681
+ this.insertIndex(slotIndex);
1682
+ }
1683
+
1684
+ /**
1685
+ * Parser-only append for slots produced by parseSlotBits. Those bits are
1686
+ * either immediate scalars or source-relative Lazy slices, so they never
1687
+ * require a GC link. The common pre-sized case writes all three buffers
1688
+ * directly; unusual key-heavy documents retain the general growth path.
1689
+ */
1690
+ appendParsedSlot(keyStart: usize, keyEnd: usize, bits: u64): void {
1691
+ this._rawStart = 0;
1692
+ this._rawEnd = 0;
1693
+ const slotIndex = this._vused;
1694
+ const len = <i32>((keyEnd - keyStart) >> 1);
1695
+ const keyPos = this._kused;
1696
+ if (
1697
+ keyPos + 1 + len <= this._kbuf.length &&
1698
+ slotIndex < this._kpos.length &&
1699
+ slotIndex < this._vals.length
1700
+ ) {
1701
+ const keyBuf = changetype<usize>(this._kbuf);
1702
+ store<u16>(keyBuf + ((<usize>keyPos) << 1), <u16>len);
1703
+ if (len)
1704
+ memory.copy(
1705
+ keyBuf + ((<usize>(keyPos + 1)) << 1),
1706
+ keyStart,
1707
+ (<usize>len) << 1,
1708
+ );
1709
+ unchecked((this._kpos[slotIndex] = keyPos));
1710
+ unchecked((this._vals[slotIndex] = bits));
1711
+ this._kused = keyPos + 1 + len;
1712
+ this._vused = slotIndex + 1;
1713
+ return;
1714
+ }
1715
+ this.pushKeyBytes(keyStart, keyEnd, slotIndex);
1716
+ this.pushValSlot(bits);
1717
+ }
1718
+
1719
+ /**
1720
+ * Appends a key and value (any T) without a duplicate-key check. Eagerly
1721
+ * boxes the value into a slot.
1722
+ */
1723
+ appendRaw<T>(keyStart: usize, keyEnd: usize, value: T): void {
1724
+ this.appendRawSlot(keyStart, keyEnd, JSON.Value.bitsFrom<T>(value));
1725
+ }
1726
+
1727
+ /** Inserts a single slot into an already-built index. */
1728
+ private insertIndex(slotIndex: i32): void {
1729
+ const idx = this._index;
1730
+ if (idx === null) return;
1731
+ const mask = this._indexMask;
1732
+ let slot = <i32>(this.keyHashAt(slotIndex) & (<u32>mask));
1733
+ const start = slot;
1734
+ while (unchecked(idx[slot]) != 0) {
1735
+ const entry = unchecked(idx[slot]) - 1;
1736
+ if (this.slotEqualsSlot(entry, slotIndex)) {
1737
+ unchecked((idx[slot] = slotIndex + 1));
1738
+ return;
1739
+ }
1740
+ slot = (slot + 1) & mask;
1741
+ if (slot == start) {
1742
+ this._index = null;
1743
+ return;
1744
+ }
1745
+ }
1746
+ unchecked((idx[slot] = slotIndex + 1));
1747
+ // buildIndex() sizes the table at >2x the entry count (load < 0.5), but it
1748
+ // only runs lazily; appends since then go through here without resizing.
1749
+ // Once we cross half load, drop the index so the next access rebuilds it at
1750
+ // double capacity — this keeps an empty slot available for every probe.
1751
+ // Without it a small (e.g. cap-2) table fills and indexOf() spins forever.
1752
+ if ((slotIndex + 1) << 1 > mask + 1) this._index = null;
1753
+ }
1754
+
1755
+ /** Builds (once) and returns the lazy key -> position index. */
1756
+ private buildIndex(): StaticArray<i32> {
1757
+ let idx = this._index;
1758
+ if (idx === null) {
1759
+ const used = this._vused;
1760
+ let cap = 2;
1761
+ while (cap <= used << 1) cap <<= 1;
1762
+ idx = new StaticArray<i32>(cap);
1763
+ const mask = cap - 1;
1764
+ for (let i = 0; i < used; i++) {
1765
+ const keyPos = unchecked(this._kpos[i]);
1766
+ const buf = changetype<usize>(this._kbuf) + ((<usize>keyPos) << 1);
1767
+ const len = <i32>load<u16>(buf);
1768
+ let slot = <i32>(hashUtf16(buf + 2, len) & (<u32>mask));
1769
+ while (unchecked(idx[slot]) != 0) {
1770
+ const entry = unchecked(idx[slot]) - 1;
1771
+ if (this.slotEqualsSlot(entry, i)) {
1772
+ unchecked((idx[slot] = i + 1));
1773
+ break;
1774
+ }
1775
+ slot = (slot + 1) & mask;
1776
+ }
1777
+ if (unchecked(idx[slot]) == 0) unchecked((idx[slot] = i + 1));
1778
+ }
1779
+ this._index = idx;
1780
+ this._indexMask = mask;
1781
+ }
1782
+ return idx;
1783
+ }
1784
+
1785
+ /**
1786
+ * Sets a key-value pair in the object, overwriting any existing value.
1787
+ * @param key - The string key
1788
+ * @param value - The value (will be wrapped in JSON.Value)
1789
+ */
1790
+ set<T>(key: string, value: T): void {
1791
+ this._rawStart = 0;
1792
+ this._rawEnd = 0;
1793
+ const bits = JSON.Value.bitsFrom<T>(value);
1794
+ const i = this.indexOf(key);
1795
+ if (i >= 0) {
1796
+ this.storeSlot(i, bits);
1030
1797
  } else {
1798
+ const slotIndex = this._vused;
1031
1799
  this.pushKeyBytes(
1032
1800
  changetype<usize>(key),
1033
1801
  changetype<usize>(key) + ((<usize>key.length) << 1),
1802
+ slotIndex,
1034
1803
  );
1035
- this._vals.push(JSON.Value.from<T>(value));
1036
- idx.set(key, this._vals.length - 1);
1804
+ this.pushValSlot(bits);
1805
+ this.insertIndex(slotIndex);
1037
1806
  }
1038
1807
  }
1039
1808
 
1040
1809
  /**
1041
- * Gets a value by key.
1810
+ * Gets a value by key as a JSON.Value (dynamic access).
1042
1811
  * @param key - The key to look up
1043
1812
  * @returns The JSON.Value or null if not found
1044
1813
  */
1045
- @inline get(key: string): JSON.Value | null {
1046
- const idx = this.buildIndex();
1047
- return idx.has(key) ? unchecked(this._vals[idx.get(key)]) : null;
1814
+ get(key: string): JSON.Value | null {
1815
+ const i = this.indexOf(key);
1816
+ if (i < 0) return null;
1817
+ const slot = unchecked(this._vals[i]);
1818
+ if (JSON.Value.slotIsLazy(slot)) {
1819
+ // Hand back a self-contained lazy value (its own slice + anchor) so it
1820
+ // can materialize independently of this object.
1821
+ const base = changetype<usize>(this._src);
1822
+ const start = JSON.Value.slotPtr(slot, base);
1823
+ const end = JSON.Value.slotEnd(slot, base, this.srcEnd());
1824
+ return JSON.Value.fromSlice(start, end, this._src);
1825
+ }
1826
+ return JSON.Value.fromBits(slot);
1827
+ }
1828
+
1829
+ /**
1830
+ * Gets a value by key directly as `T`, with no intermediate JSON.Value
1831
+ * allocation. A deferred slot is parsed (and cached) on first access; an
1832
+ * absent key returns the type's default (null / 0 / false).
1833
+ *
1834
+ * Named `getAs` rather than `get<T>` because AssemblyScript has no method
1835
+ * overloading - `get(key)` (dynamic) and a typed `get` can't share a name.
1836
+ * @param key - The key to look up
1837
+ */
1838
+ getAs<T>(key: string): T {
1839
+ const i = this.indexOf(key);
1840
+ if (i < 0) return __zero<T>();
1841
+ let slot = unchecked(this._vals[i]);
1842
+ if (JSON.Value.slotIsLazy(slot)) slot = this.materializeSlot(i);
1843
+ return JSON.Value.decodeBits<T>(slot);
1048
1844
  }
1049
1845
 
1050
1846
  /**
@@ -1052,8 +1848,8 @@ export namespace JSON {
1052
1848
  * @param key - The key to check
1053
1849
  * @returns true if the key exists
1054
1850
  */
1055
- @inline has(key: string): bool {
1056
- return this.buildIndex().has(key);
1851
+ has(key: string): bool {
1852
+ return this.indexOf(key) >= 0;
1057
1853
  }
1058
1854
 
1059
1855
  /**
@@ -1062,34 +1858,56 @@ export namespace JSON {
1062
1858
  * @returns true if the key was found and deleted
1063
1859
  */
1064
1860
  delete(key: string): bool {
1065
- const idx = this.buildIndex();
1066
- if (!idx.has(key)) return false;
1067
- const removed = idx.get(key);
1861
+ const removed = this.indexOf(key);
1862
+ if (removed < 0) return false;
1863
+ this._rawStart = 0;
1864
+ this._rawEnd = 0;
1068
1865
  const keys = this.keys();
1069
- const vals = this._vals;
1866
+ const oldVals = this._vals;
1867
+ const n = this._vused;
1070
1868
  this._kbuf = EMPTY_KEYS;
1071
1869
  this._kused = 0;
1072
- const newVals = new Array<JSON.Value>();
1073
- for (let j = 0; j < keys.length; j++) {
1870
+ this._kpos = EMPTY_I32S;
1871
+ this._vals = EMPTY_VALS;
1872
+ this._vused = 0;
1873
+ for (let j = 0; j < n; j++) {
1074
1874
  if (j == removed) continue;
1075
1875
  const k = unchecked(keys[j]);
1076
1876
  this.pushKeyBytes(
1077
1877
  changetype<usize>(k),
1078
1878
  changetype<usize>(k) + ((<usize>k.length) << 1),
1879
+ this._vused,
1079
1880
  );
1080
- newVals.push(unchecked(vals[j]));
1881
+ this.pushValSlot(unchecked(oldVals[j]));
1081
1882
  }
1082
- this._vals = newVals;
1083
1883
  this._index = null;
1884
+ this._indexMask = 0;
1084
1885
  return true;
1085
1886
  }
1086
1887
 
1888
+ /**
1889
+ * Removes all entries. Backing key/value buffer capacity is kept so a
1890
+ * subsequent parse or insert reuses it without re-allocating - this is what
1891
+ * makes `JSON.parse<JSON.Obj>(data, out)` allocation-light. `__visit` only
1892
+ * traces `[0, _vused)`, so resetting the used counters drops the old slots
1893
+ * from GC tracing safely.
1894
+ */
1895
+ clear(): void {
1896
+ this._kused = 0;
1897
+ this._vused = 0;
1898
+ this._src = "";
1899
+ this._rawStart = 0;
1900
+ this._rawEnd = 0;
1901
+ this._index = null;
1902
+ this._indexMask = 0;
1903
+ }
1904
+
1087
1905
  /**
1088
1906
  * Gets all keys in the object.
1089
1907
  * @returns Array of string keys (in insertion order)
1090
1908
  */
1091
1909
  keys(): string[] {
1092
- const out = new Array<string>(this._vals.length);
1910
+ const out = new Array<string>(this._vused);
1093
1911
  const buf = changetype<usize>(this._kbuf);
1094
1912
  const used = this._kused;
1095
1913
  let pos = 0;
@@ -1106,15 +1924,29 @@ export namespace JSON {
1106
1924
  * Gets all values in the object.
1107
1925
  * @returns Array of JSON.Value instances (in insertion order)
1108
1926
  */
1109
- @inline values(): JSON.Value[] {
1110
- return this._vals.slice();
1927
+ values(): JSON.Value[] {
1928
+ const n = this._vused;
1929
+ const out = new Array<JSON.Value>(n);
1930
+ const base = changetype<usize>(this._src);
1931
+ const srcEnd = this.srcEnd();
1932
+ for (let i = 0; i < n; i++) {
1933
+ const slot = unchecked(this._vals[i]);
1934
+ if (JSON.Value.slotIsLazy(slot)) {
1935
+ const start = JSON.Value.slotPtr(slot, base);
1936
+ const end = JSON.Value.slotEnd(slot, base, srcEnd);
1937
+ unchecked((out[i] = JSON.Value.fromSlice(start, end, this._src)));
1938
+ } else {
1939
+ unchecked((out[i] = JSON.Value.fromBits(slot)));
1940
+ }
1941
+ }
1942
+ return out;
1111
1943
  }
1112
1944
 
1113
1945
  /**
1114
1946
  * Serializes the object to a JSON string.
1115
1947
  * @returns JSON string representation
1116
1948
  */
1117
- @inline toString(): string {
1949
+ toString(): string {
1118
1950
  return JSON.stringify(this);
1119
1951
  }
1120
1952
 
@@ -1133,7 +1965,8 @@ export namespace JSON {
1133
1965
 
1134
1966
  const keys = value.keys();
1135
1967
  const values = value.values();
1136
- for (let i = 0; i < keys.length; i++) {
1968
+ const len = keys.length;
1969
+ for (let i = 0; i < len; i++) {
1137
1970
  out.set(unchecked(keys[i]), unchecked(values[i]));
1138
1971
  }
1139
1972
  return out;
@@ -1147,6 +1980,538 @@ export namespace JSON {
1147
1980
  }
1148
1981
  return parsed.get<JSON.Obj>();
1149
1982
  }
1983
+
1984
+
1985
+ @unsafe private __visit(cookie: u32): void {
1986
+ __visit(changetype<usize>(this._kbuf), cookie);
1987
+ __visit(changetype<usize>(this._kpos), cookie);
1988
+ __visit(changetype<usize>(this._vals), cookie);
1989
+ __visit(changetype<usize>(this._src), cookie);
1990
+ __visit(changetype<usize>(this._index), cookie); // null-safe in rt
1991
+ const vals = this._vals;
1992
+ const n = this._vused;
1993
+ for (let i = 0; i < n; i++) {
1994
+ const w = unchecked(vals[i]);
1995
+ if (!valBoxed(w)) continue; // raw f64, no reference
1996
+ const tag = valTag(w);
1997
+ if (tag == JSON.Types.Lazy) continue; // interior _src pointer
1998
+ if (tag >= JSON.Types.String) {
1999
+ __visit(valPtr(w), cookie);
2000
+ } else if (
2001
+ (tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
2002
+ w & VAL_BOX64
2003
+ ) {
2004
+ __visit(valPtr(w), cookie); // heap-spilled 64-bit int
2005
+ }
2006
+ }
2007
+ }
2008
+ }
2009
+
2010
+ /**
2011
+ * Dynamic JSON array with JSON.Value elements, backed by the same flat
2012
+ * NaN-boxed u64 slot buffer as JSON.Obj (no per-element JSON.Value objects).
2013
+ * Deferred string/array/object elements are parsed on first access; untouched
2014
+ * elements serialize straight from their source bytes.
2015
+ *
2016
+ * Index with `arr.at(i)` (returns a JSON.Value) or read typed via `getAs<T>(i)`.
2017
+ *
2018
+ * @example
2019
+ * ```typescript
2020
+ * const arr = JSON.parse<JSON.Arr>('[1,"two",[3,4]]');
2021
+ * arr.at(0).get<f64>(); // 1
2022
+ * arr.getAs<string>(1); // "two"
2023
+ * arr.getAs<JSON.Arr>(2).at(0); // 3
2024
+ * ```
2025
+ */
2026
+ @final export class Arr {
2027
+ // Same flat slot model as JSON.Obj, without the key buffer. See JSON.Obj for
2028
+ // the slot encoding, the custom `__visit`, and the `__link` write barrier.
2029
+ _vals: StaticArray<u64> = EMPTY_VALS;
2030
+ _vused: i32 = 0;
2031
+ /** Source string the lazy slot pointers index into; anchors it for GC. */
2032
+ _src: string = "";
2033
+
2034
+ constructor() {}
2035
+
2036
+ /** Number of elements. */
2037
+ get length(): i32 {
2038
+ return this._vused;
2039
+ }
2040
+
2041
+ /**
2042
+ * Removes all elements, keeping the value-slot buffer capacity so a
2043
+ * subsequent parse or push reuses it - powers `JSON.parse<JSON.Arr>(data,
2044
+ * out)`. `__visit` only traces `[0, _vused)`, so this is GC-safe.
2045
+ */
2046
+ clear(): void {
2047
+ this._vused = 0;
2048
+ this._src = "";
2049
+ }
2050
+
2051
+ /** Grows the value-slot buffer to hold at least `need` slots. */
2052
+ private ensureValCap(need: i32): void {
2053
+ const cap = this._vals.length;
2054
+ if (cap >= need) return;
2055
+ let n = cap ? cap : 8;
2056
+ while (n < need) n <<= 1;
2057
+ const nb = new StaticArray<u64>(n);
2058
+ if (this._vused)
2059
+ memory.copy(
2060
+ changetype<usize>(nb),
2061
+ changetype<usize>(this._vals),
2062
+ (<usize>this._vused) << 3,
2063
+ );
2064
+ this._vals = nb;
2065
+ }
2066
+
2067
+ /** Writes a slot and, if it carries a managed pointer, links it for the GC. */
2068
+ private storeSlot(i: i32, bits: u64): void {
2069
+ unchecked((this._vals[i] = bits));
2070
+ if (valBoxed(bits)) {
2071
+ const tag = valTag(bits);
2072
+ if (
2073
+ tag != JSON.Types.Lazy &&
2074
+ (tag >= JSON.Types.String ||
2075
+ ((tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
2076
+ bits & VAL_BOX64))
2077
+ ) {
2078
+ __link(changetype<usize>(this), valPtr(bits), false);
2079
+ }
2080
+ }
2081
+ }
2082
+
2083
+ /** Appends a value slot (raw NaN-boxed bits). Deserializer entry point. */
2084
+ pushRawSlot(bits: u64): void {
2085
+ const pos = this._vused;
2086
+ this.ensureValCap(pos + 1);
2087
+ this._vused = pos + 1;
2088
+ this.storeSlot(pos, bits);
2089
+ }
2090
+
2091
+ /** End pointer of the source buffer (upper bound for scanning a lazy slot). */
2092
+ private srcEnd(): usize {
2093
+ return changetype<usize>(this._src) + ((<usize>this._src.length) << 1);
2094
+ }
2095
+
2096
+ /** Parses a lazy slot in place, caching the concrete box, and returns it. */
2097
+ private materializeSlot(i: i32): u64 {
2098
+ const slot = unchecked(this._vals[i]);
2099
+ if (!JSON.Value.slotIsLazy(slot)) return slot;
2100
+ const base = changetype<usize>(this._src);
2101
+ const start = JSON.Value.slotPtr(slot, base);
2102
+ const end = JSON.Value.slotEnd(slot, base, this.srcEnd());
2103
+ const bits = JSON.Value.parseSliceBits(start, end, this._src);
2104
+ this.storeSlot(i, bits);
2105
+ return bits;
2106
+ }
2107
+
2108
+ /** Element access as a JSON.Value: `arr.at(i)`. */
2109
+ at(index: i32): JSON.Value {
2110
+ if (<u32>index >= <u32>this._vused) throw new Error("Index out of range");
2111
+ const slot = unchecked(this._vals[index]);
2112
+ if (JSON.Value.slotIsLazy(slot)) {
2113
+ const base = changetype<usize>(this._src);
2114
+ const start = JSON.Value.slotPtr(slot, base);
2115
+ const end = JSON.Value.slotEnd(slot, base, this.srcEnd());
2116
+ return JSON.Value.fromSlice(start, end, this._src);
2117
+ }
2118
+ return JSON.Value.fromBits(slot);
2119
+ }
2120
+
2121
+ /**
2122
+ * Reads element `i` directly as `T`, with no intermediate JSON.Value
2123
+ * allocation. A deferred slot is parsed (and cached) on first access.
2124
+ * @param i - element index
2125
+ */
2126
+ getAs<T>(i: i32): T {
2127
+ let slot = unchecked(this._vals[i]);
2128
+ if (JSON.Value.slotIsLazy(slot)) slot = this.materializeSlot(i);
2129
+ return JSON.Value.decodeBits<T>(slot);
2130
+ }
2131
+
2132
+ /** Appends a value (any T), eagerly boxed into a slot. */
2133
+ push<T>(value: T): void {
2134
+ this.pushRawSlot(JSON.Value.bitsFrom<T>(value));
2135
+ }
2136
+
2137
+ /** Overwrites element `i`. */
2138
+ set<T>(i: i32, value: T): void {
2139
+ this.storeSlot(i, JSON.Value.bitsFrom<T>(value));
2140
+ }
2141
+
2142
+ /**
2143
+ * Bounds-checked element access via `arr[i]` - returns a `JSON.Value`
2144
+ * (allocating), mirroring `at(i)`. For an allocation-free typed read use
2145
+ * `getAs<T>(i)`.
2146
+ */
2147
+ @operator("[]") private __get(index: i32): JSON.Value {
2148
+ return this.at(index);
2149
+ }
2150
+
2151
+ /** Element assignment via `arr[i] = value` (any `JSON.Value`). */
2152
+ @operator("[]=") private __set(index: i32, value: JSON.Value): void {
2153
+ this.set<JSON.Value>(index, value);
2154
+ }
2155
+
2156
+ /** Serializes the array to a JSON string. */
2157
+ toString(): string {
2158
+ return JSON.stringify(this);
2159
+ }
2160
+
2161
+ /** Creates a JSON.Arr from a JSON.Value[] (or returns an existing one). */
2162
+ static from<T>(value: T): JSON.Arr {
2163
+ if (value instanceof JSON.Arr) return value;
2164
+ // @ts-expect-error: handled by the isArray guard below
2165
+ if (isArray<T>() && idof<valueof<T>>() == idof<JSON.Value>()) {
2166
+ const out = new JSON.Arr();
2167
+ // @ts-expect-error: T is JSON.Value[] here
2168
+ const arr = changetype<JSON.Value[]>(value);
2169
+ const len = arr.length;
2170
+ for (let i = 0; i < len; i++) {
2171
+ out.pushRawSlot(JSON.Value.bitsFrom<JSON.Value>(unchecked(arr[i])));
2172
+ }
2173
+ return out;
2174
+ }
2175
+ throw new Error("JSON.Arr.from expects a JSON.Value[]");
2176
+ }
2177
+
2178
+ // ---- Array-like API: slot-optimized ports of the AssemblyScript stdlib ----
2179
+ // Slot-shuffling ops (reverse/fill/copyWithin/pop/shift/unshift/slice/splice)
2180
+ // move the raw u64 slots directly - no per-element JSON.Value - and keep any
2181
+ // managed pointers linked to the same owner. New single-source results share
2182
+ // `_src`, so copied deferred ranges stay lazy. Callbacks get a JSON.Value view
2183
+ // of each element via `at(i)`.
2184
+
2185
+ /** Concrete, source-independent bits for slot `i` (resolves a deferred slot
2186
+ * without caching it back into this array - used by cross-source ops). */
2187
+ private resolvedBits(i: i32): u64 {
2188
+ const slot = unchecked(this._vals[i]);
2189
+ if (!JSON.Value.slotIsLazy(slot)) return slot;
2190
+ const base = changetype<usize>(this._src);
2191
+ const start = JSON.Value.slotPtr(slot, base);
2192
+ const end = JSON.Value.slotEnd(slot, base, this.srcEnd());
2193
+ return JSON.Value.parseSliceBits(start, end, this._src);
2194
+ }
2195
+
2196
+ /** Appends `count` raw slots from `this[from..]` into a fresh `dst`, sharing
2197
+ * `_src` so copied deferred ranges still resolve. */
2198
+ private copyInto(dst: JSON.Arr, from: i32, count: i32): void {
2199
+ if (count <= 0) return;
2200
+ dst._src = this._src;
2201
+ const at = dst._vused;
2202
+ dst.ensureValCap(at + count);
2203
+ for (let k = 0; k < count; k++)
2204
+ dst.storeSlot(at + k, unchecked(this._vals[from + k]));
2205
+ dst._vused = at + count;
2206
+ }
2207
+
2208
+ /** Truncates (drops the tail) or extends (pads with `null`) the array. */
2209
+ set length(newLength: i32) {
2210
+ if (newLength < 0) throw new Error("Invalid array length");
2211
+ const used = this._vused;
2212
+ if (newLength <= used) {
2213
+ this._vused = newLength;
2214
+ return;
2215
+ }
2216
+ this.ensureValCap(newLength);
2217
+ for (let i = used; i < newLength; i++)
2218
+ unchecked((this._vals[i] = JSON.Value.nullBits()));
2219
+ this._vused = newLength;
2220
+ }
2221
+
2222
+ /** Removes and returns the last element. */
2223
+ pop(): JSON.Value {
2224
+ const n = this._vused;
2225
+ if (n == 0) throw new Error("pop from empty JSON.Arr");
2226
+ const v = this.at(n - 1);
2227
+ this._vused = n - 1;
2228
+ return v;
2229
+ }
2230
+
2231
+ /** Removes and returns the first element, shifting the rest down. */
2232
+ shift(): JSON.Value {
2233
+ const n = this._vused;
2234
+ if (n == 0) throw new Error("shift from empty JSON.Arr");
2235
+ const v = this.at(0);
2236
+ const base = changetype<usize>(this._vals);
2237
+ memory.copy(base, base + 8, (<usize>(n - 1)) << 3);
2238
+ this._vused = n - 1;
2239
+ return v;
2240
+ }
2241
+
2242
+ /** Prepends `value`, shifting existing elements up. Returns the new length. */
2243
+ unshift<T>(value: T): i32 {
2244
+ const n = this._vused;
2245
+ this.ensureValCap(n + 1);
2246
+ const base = changetype<usize>(this._vals);
2247
+ if (n) memory.copy(base + 8, base, (<usize>n) << 3);
2248
+ this._vused = n + 1;
2249
+ this.storeSlot(0, JSON.Value.bitsFrom<T>(value));
2250
+ return n + 1;
2251
+ }
2252
+
2253
+ /** Reverses the elements in place (slot swap). */
2254
+ reverse(): JSON.Arr {
2255
+ const vals = this._vals;
2256
+ let lo = 0;
2257
+ let hi = this._vused - 1;
2258
+ while (lo < hi) {
2259
+ const t = unchecked(vals[lo]);
2260
+ unchecked((vals[lo] = unchecked(vals[hi])));
2261
+ unchecked((vals[hi] = t));
2262
+ lo++;
2263
+ hi--;
2264
+ }
2265
+ return this;
2266
+ }
2267
+
2268
+ /** Fills `[start, end)` with `value`. */
2269
+ fill<T>(value: T, start: i32 = 0, end: i32 = i32.MAX_VALUE): JSON.Arr {
2270
+ const n = this._vused;
2271
+ let s = start < 0 ? max(n + start, 0) : min(start, n);
2272
+ const e = end < 0 ? max(n + end, 0) : min(end, n);
2273
+ const bits = JSON.Value.bitsFrom<T>(value);
2274
+ for (; s < e; s++) this.storeSlot(s, bits);
2275
+ return this;
2276
+ }
2277
+
2278
+ /** Copies the slot block `[start, end)` to `target`, in place. */
2279
+ copyWithin(target: i32, start: i32, end: i32 = i32.MAX_VALUE): JSON.Arr {
2280
+ const n = this._vused;
2281
+ const t = target < 0 ? max(n + target, 0) : min(target, n);
2282
+ const s = start < 0 ? max(n + start, 0) : min(start, n);
2283
+ const e = end < 0 ? max(n + end, 0) : min(end, n);
2284
+ const count = min(e - s, n - t);
2285
+ if (count > 0) {
2286
+ const base = changetype<usize>(this._vals);
2287
+ memory.copy(
2288
+ base + ((<usize>t) << 3),
2289
+ base + ((<usize>s) << 3),
2290
+ (<usize>count) << 3,
2291
+ );
2292
+ }
2293
+ return this;
2294
+ }
2295
+
2296
+ /** Returns a new JSON.Arr with the elements in `[start, end)` (lazy-preserving). */
2297
+ slice(start: i32 = 0, end: i32 = i32.MAX_VALUE): JSON.Arr {
2298
+ const n = this._vused;
2299
+ const s = start < 0 ? max(n + start, 0) : min(start, n);
2300
+ const e = end < 0 ? max(n + end, 0) : min(end, n);
2301
+ const out = new JSON.Arr();
2302
+ this.copyInto(out, s, e - s);
2303
+ return out;
2304
+ }
2305
+
2306
+ /** Removes `deleteCount` elements at `start`; returns them as a new JSON.Arr. */
2307
+ splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): JSON.Arr {
2308
+ const n = this._vused;
2309
+ const s = start < 0 ? max(n + start, 0) : min(start, n);
2310
+ const d = max(min(deleteCount, n - s), 0);
2311
+ const removed = new JSON.Arr();
2312
+ this.copyInto(removed, s, d);
2313
+ const tail = n - (s + d);
2314
+ if (tail > 0) {
2315
+ const base = changetype<usize>(this._vals);
2316
+ memory.copy(
2317
+ base + ((<usize>s) << 3),
2318
+ base + ((<usize>(s + d)) << 3),
2319
+ (<usize>tail) << 3,
2320
+ );
2321
+ }
2322
+ this._vused = n - d;
2323
+ return removed;
2324
+ }
2325
+
2326
+ /** Returns a new JSON.Arr = this followed by `other` (resolves deferred slots
2327
+ * since the two sources can't share one `_src`). */
2328
+ concat(other: JSON.Arr): JSON.Arr {
2329
+ const out = new JSON.Arr();
2330
+ const a = this._vused;
2331
+ const b = other._vused;
2332
+ out.ensureValCap(a + b);
2333
+ for (let i = 0; i < a; i++) out.pushRawSlot(this.resolvedBits(i));
2334
+ for (let i = 0; i < b; i++) out.pushRawSlot(other.resolvedBits(i));
2335
+ return out;
2336
+ }
2337
+
2338
+ /** First index of `value` (typed compare via getAs<T>), or -1. */
2339
+ indexOf<T>(value: T, fromIndex: i32 = 0): i32 {
2340
+ const n = this._vused;
2341
+ let i = fromIndex < 0 ? max(n + fromIndex, 0) : fromIndex;
2342
+ for (; i < n; i++) if (this.getAs<T>(i) == value) return i;
2343
+ return -1;
2344
+ }
2345
+
2346
+ /** Last index of `value`, searching backwards, or -1. */
2347
+ lastIndexOf<T>(value: T, fromIndex: i32 = i32.MAX_VALUE): i32 {
2348
+ const n = this._vused;
2349
+ let i = fromIndex < 0 ? n + fromIndex : min(fromIndex, n - 1);
2350
+ for (; i >= 0; i--) if (this.getAs<T>(i) == value) return i;
2351
+ return -1;
2352
+ }
2353
+
2354
+ /** Whether `value` is present. */
2355
+ includes<T>(value: T, fromIndex: i32 = 0): bool {
2356
+ return this.indexOf<T>(value, fromIndex) >= 0;
2357
+ }
2358
+
2359
+ /** Calls `fn` for each element. */
2360
+ forEach(
2361
+ fn: (value: JSON.Value, index: i32, array: JSON.Arr) => void,
2362
+ ): void {
2363
+ for (let i = 0, n = this._vused; i < n; i++) fn(this.at(i), i, this);
2364
+ }
2365
+
2366
+ /** New JSON.Arr of `fn`'s results. */
2367
+ map(
2368
+ fn: (value: JSON.Value, index: i32, array: JSON.Arr) => JSON.Value,
2369
+ ): JSON.Arr {
2370
+ const n = this._vused;
2371
+ const out = new JSON.Arr();
2372
+ out.ensureValCap(n);
2373
+ for (let i = 0; i < n; i++) out.push<JSON.Value>(fn(this.at(i), i, this));
2374
+ return out;
2375
+ }
2376
+
2377
+ /** New JSON.Arr of elements passing `fn` (lazy-preserving). */
2378
+ filter(
2379
+ fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool,
2380
+ ): JSON.Arr {
2381
+ const out = new JSON.Arr();
2382
+ for (let i = 0, n = this._vused; i < n; i++)
2383
+ if (fn(this.at(i), i, this)) this.copyInto(out, i, 1);
2384
+ return out;
2385
+ }
2386
+
2387
+ /** First element passing `fn`, or `null`. */
2388
+ find(
2389
+ fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool,
2390
+ ): JSON.Value | null {
2391
+ for (let i = 0, n = this._vused; i < n; i++) {
2392
+ const v = this.at(i);
2393
+ if (fn(v, i, this)) return v;
2394
+ }
2395
+ return null;
2396
+ }
2397
+
2398
+ /** Index of the first element passing `fn`, or -1. */
2399
+ findIndex(
2400
+ fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool,
2401
+ ): i32 {
2402
+ for (let i = 0, n = this._vused; i < n; i++)
2403
+ if (fn(this.at(i), i, this)) return i;
2404
+ return -1;
2405
+ }
2406
+
2407
+ /** Last element passing `fn`, or `null`. */
2408
+ findLast(
2409
+ fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool,
2410
+ ): JSON.Value | null {
2411
+ for (let i = this._vused - 1; i >= 0; i--) {
2412
+ const v = this.at(i);
2413
+ if (fn(v, i, this)) return v;
2414
+ }
2415
+ return null;
2416
+ }
2417
+
2418
+ /** Index of the last element passing `fn`, or -1. */
2419
+ findLastIndex(
2420
+ fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool,
2421
+ ): i32 {
2422
+ for (let i = this._vused - 1; i >= 0; i--)
2423
+ if (fn(this.at(i), i, this)) return i;
2424
+ return -1;
2425
+ }
2426
+
2427
+ /** Whether every element passes `fn`. */
2428
+ every(fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool): bool {
2429
+ for (let i = 0, n = this._vused; i < n; i++)
2430
+ if (!fn(this.at(i), i, this)) return false;
2431
+ return true;
2432
+ }
2433
+
2434
+ /** Whether any element passes `fn`. */
2435
+ some(fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool): bool {
2436
+ for (let i = 0, n = this._vused; i < n; i++)
2437
+ if (fn(this.at(i), i, this)) return true;
2438
+ return false;
2439
+ }
2440
+
2441
+ /** Left fold. */
2442
+ reduce<U>(
2443
+ fn: (acc: U, value: JSON.Value, index: i32, array: JSON.Arr) => U,
2444
+ initialValue: U,
2445
+ ): U {
2446
+ let acc = initialValue;
2447
+ for (let i = 0, n = this._vused; i < n; i++)
2448
+ acc = fn(acc, this.at(i), i, this);
2449
+ return acc;
2450
+ }
2451
+
2452
+ /** Right fold. */
2453
+ reduceRight<U>(
2454
+ fn: (acc: U, value: JSON.Value, index: i32, array: JSON.Arr) => U,
2455
+ initialValue: U,
2456
+ ): U {
2457
+ let acc = initialValue;
2458
+ for (let i = this._vused - 1; i >= 0; i--)
2459
+ acc = fn(acc, this.at(i), i, this);
2460
+ return acc;
2461
+ }
2462
+
2463
+ /** Sorts in place by `comparator(a, b)` (materializes for comparison). */
2464
+ sort(comparator: (a: JSON.Value, b: JSON.Value) => i32): JSON.Arr {
2465
+ const n = this._vused;
2466
+ if (n < 2) return this;
2467
+ const view = new Array<JSON.Value>(n);
2468
+ for (let i = 0; i < n; i++) unchecked((view[i] = this.at(i)));
2469
+ view.sort(comparator);
2470
+ for (let i = 0; i < n; i++)
2471
+ this.storeSlot(i, JSON.Value.bitsFrom<JSON.Value>(unchecked(view[i])));
2472
+ return this;
2473
+ }
2474
+
2475
+ /** JS-parity element string: strings unquoted, null -> "", everything else
2476
+ * via ES-exact JSON (numbers drop a trailing `.0`). */
2477
+ private elemStr(i: i32): string {
2478
+ const v = this.at(i);
2479
+ const t = v.type;
2480
+ if (t == JSON.Types.String) return v.get<string>();
2481
+ if (t == JSON.Types.Null) return "";
2482
+ return JSON.stringify(v);
2483
+ }
2484
+
2485
+ /** Joins the elements with `separator` (JS `Array#join` semantics). */
2486
+ join(separator: string = ","): string {
2487
+ const n = this._vused;
2488
+ if (n == 0) return "";
2489
+ let out = this.elemStr(0);
2490
+ for (let i = 1; i < n; i++) out += separator + this.elemStr(i);
2491
+ return out;
2492
+ }
2493
+
2494
+ // See JSON.Obj.__visit - same custom GC visitor for the slot buffer.
2495
+ @unsafe private __visit(cookie: u32): void {
2496
+ __visit(changetype<usize>(this._vals), cookie);
2497
+ __visit(changetype<usize>(this._src), cookie);
2498
+ const vals = this._vals;
2499
+ const n = this._vused;
2500
+ for (let i = 0; i < n; i++) {
2501
+ const w = unchecked(vals[i]);
2502
+ if (!valBoxed(w)) continue;
2503
+ const tag = valTag(w);
2504
+ if (tag == JSON.Types.Lazy) continue;
2505
+ if (tag >= JSON.Types.String) {
2506
+ __visit(valPtr(w), cookie);
2507
+ } else if (
2508
+ (tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
2509
+ w & VAL_BOX64
2510
+ ) {
2511
+ __visit(valPtr(w), cookie);
2512
+ }
2513
+ }
2514
+ }
1150
2515
  }
1151
2516
  /**
1152
2517
  * Box for primitive types
@@ -1161,7 +2526,7 @@ export namespace JSON {
1161
2526
  * @param value T
1162
2527
  * @returns this
1163
2528
  */
1164
- @inline set(value: T): Box<T> {
2529
+ set(value: T): Box<T> {
1165
2530
  this.value = value;
1166
2531
  return this;
1167
2532
  }
@@ -1176,7 +2541,7 @@ export namespace JSON {
1176
2541
  * @param from T
1177
2542
  * @returns Box<T> | null
1178
2543
  */
1179
- @inline static fromValue<T>(value: JSON.Value): Box<T> | null {
2544
+ static fromValue<T>(value: JSON.Value): Box<T> | null {
1180
2545
  if (!(value instanceof JSON.Value))
1181
2546
  throw new Error("value must be of type JSON.Value");
1182
2547
  if (value.type === JSON.Types.Null) return null;
@@ -1195,7 +2560,7 @@ export namespace JSON {
1195
2560
  * @param from T
1196
2561
  * @returns Box<T>
1197
2562
  */
1198
- @inline static from<T>(value: T): Box<T> {
2563
+ static from<T>(value: T): Box<T> {
1199
2564
  return new Box(value);
1200
2565
  }
1201
2566
  toString(): string {
@@ -1249,6 +2614,24 @@ export namespace JSON {
1249
2614
  }
1250
2615
  }
1251
2616
 
2617
+ /**
2618
+ * Primitive serializer for generated structs that already reserved the
2619
+ * complete worst-case output size in `__SERIALIZE`. Avoids repeating buffer
2620
+ * capacity and stack-size accounting for every integer/boolean field.
2621
+ * Internal: emitted only for non-nullable primitive fields by the transform.
2622
+ */
2623
+ function __serializePrimitiveUnsafe<T>(data: T): void {
2624
+ if (isBoolean<T>()) {
2625
+ serializeBoolUnsafe(data as bool);
2626
+ } else if (isInteger<T>()) {
2627
+ // @ts-expect-error: transform emits this only for integer T
2628
+ serializeIntegerUnsafe<T>(data);
2629
+ } else {
2630
+ // Defensive fallback for hand-written transformed code.
2631
+ __serialize<T>(data);
2632
+ }
2633
+ }
2634
+
1252
2635
  /**
1253
2636
  * Deserializes JSON data directly from the buffer.
1254
2637
  * Should only be used if you know what you are doing.
@@ -1260,31 +2643,43 @@ export namespace JSON {
1260
2643
  function __deserialize<T>(srcStart: usize, srcEnd: usize, dst: usize = 0): T {
1261
2644
  // Skip leading whitespace once here so every handler below may assume
1262
2645
  // srcStart is at the first non-whitespace char. (Trailing whitespace is
1263
- // left intact composites self-trim and JSON.Raw preserves it.)
2646
+ // left intact - composites self-trim and JSON.Raw preserves it.)
1264
2647
  while (srcStart < srcEnd && JSON.Util.isSpace(load<u16>(srcStart)))
1265
2648
  srcStart += 2;
1266
2649
  if (isBoolean<T>()) {
1267
2650
  // @ts-expect-error: type
1268
- return deserializeBoolean(srcStart, srcEnd);
2651
+ const code = deserializeBooleanCode(srcStart, srcEnd);
2652
+ if (code == 0) throw new Error("Expected 'true' or 'false' in JSON");
2653
+ return code == 2;
1269
2654
  } else if (isInteger<T>()) {
1270
2655
  return isSigned<T>()
1271
2656
  ? deserializeInteger<T>(srcStart, srcEnd)
1272
2657
  : deserializeUnsigned<T>(srcStart, srcEnd);
1273
2658
  } else if (isFloat<T>()) {
1274
2659
  return deserializeFloat<T>(srcStart, srcEnd);
1275
- } else if (isString<T>()) {
1276
- if (srcEnd - srcStart < 4)
1277
- throw new Error(
1278
- "Cannot parse data as string because it was formatted incorrectly!",
1279
- );
1280
-
1281
- return deserializeString(srcStart, srcEnd) as T;
1282
2660
  } else if (
1283
2661
  isNullable<T>() &&
1284
2662
  srcEnd - srcStart == 8 &&
1285
2663
  load<u64>(srcStart) == NULL_WORD_U64
1286
2664
  ) {
2665
+ // A `null` literal must be matched before the string branch: a nullable
2666
+ // string (`string | null`) reports `isString<T>() == true`, so without
2667
+ // this `null` would be (mis)handled as a quoted string and abort. Mirrors
2668
+ // the same-ordered check in `parseInternal`. Reached by lazy-field
2669
+ // materialization, which routes every slot value through `__deserialize`.
1287
2670
  return null;
2671
+ } else if (isString<T>()) {
2672
+ if (srcEnd - srcStart < 4)
2673
+ throw new Error(
2674
+ "Cannot parse data as string because it was formatted incorrectly!",
2675
+ );
2676
+
2677
+ const value = deserializeString(srcStart, srcEnd);
2678
+ if (changetype<usize>(value) == 0) {
2679
+ takeProductionParseError();
2680
+ throw new Error("Invalid JSON string: missing surrounding quotes");
2681
+ }
2682
+ return value as T;
1288
2683
  } else {
1289
2684
  let type: nonnull<T> = changetype<nonnull<T>>(0);
1290
2685
  // @ts-expect-error: Defined by transform
@@ -1297,9 +2692,15 @@ export namespace JSON {
1297
2692
  isDefined(type.__DESERIALIZE_SLOW) ||
1298
2693
  isDefined(type.__DESERIALIZE_FAST)
1299
2694
  ) {
2695
+ const fresh = dst == 0;
1300
2696
  const out = changetype<nonnull<T>>(
1301
2697
  dst || __new(offsetof<nonnull<T>>(), idof<nonnull<T>>()),
1302
2698
  );
2699
+ // Generated fast paths may preserve omitted fields and reuse existing
2700
+ // strings/containers. Initialize only a freshly allocated object here;
2701
+ // callers that pass `dst` intentionally retain that reusable graph.
2702
+ // @ts-expect-error: Defined by transform
2703
+ if (fresh && isDefined(type.__INITIALIZE)) out.__INITIALIZE();
1303
2704
  // @ts-expect-error: Defined by transform
1304
2705
  if (isDefined(type.__DESERIALIZE_FAST)) {
1305
2706
  // @ts-expect-error: Defined by transform
@@ -1316,17 +2717,28 @@ export namespace JSON {
1316
2717
  // @ts-expect-error: Defined by transform
1317
2718
  if (isDefined(type.__DESERIALIZE_SLOW)) {
1318
2719
  // @ts-expect-error: Defined by transform
1319
- out.__DESERIALIZE_SLOW(srcStart, srcEnd, out);
2720
+ const slowEnd = out.__DESERIALIZE_SLOW(srcStart, srcEnd, out);
2721
+ if (slowEnd == 0) {
2722
+ takeProductionParseError();
2723
+ throw new Error("Malformed JSON object");
2724
+ }
2725
+ if (takeProductionParseError())
2726
+ throw new Error("Malformed nested JSON value");
1320
2727
  return out;
1321
2728
  }
1322
2729
  throw new Error(`No deserialize method defined for type ${type}`);
1323
2730
  }
1324
2731
  if (type instanceof StaticArray) {
1325
2732
  // @ts-expect-error: type
1326
- return deserializeStaticArray<T>(srcStart, srcEnd, dst);
2733
+ return deserializeStaticArray<nonnull<T>>(srcStart, srcEnd, dst) as T;
1327
2734
  } else if (type instanceof Array) {
1328
2735
  // @ts-expect-error: type
1329
- return deserializeArray<T>(srcStart, srcEnd, dst);
2736
+ const value = deserializeArray<nonnull<T>>(srcStart, srcEnd, dst) as T;
2737
+ if (changetype<usize>(value) == 0) {
2738
+ takeProductionParseError();
2739
+ throw new Error("Unterminated array in JSON");
2740
+ }
2741
+ return value;
1330
2742
  } else if (
1331
2743
  type instanceof Int8Array ||
1332
2744
  type instanceof Uint8Array ||
@@ -1348,7 +2760,12 @@ export namespace JSON {
1348
2760
  return deserializeSet<T>(srcStart, srcEnd, dst);
1349
2761
  } else if (type instanceof Map) {
1350
2762
  // @ts-expect-error: type
1351
- return deserializeMap<T>(srcStart, srcEnd, dst);
2763
+ const value = deserializeMap<T>(srcStart, srcEnd, dst);
2764
+ if (changetype<usize>(value) == 0) {
2765
+ takeProductionParseError();
2766
+ throw new Error("Malformed JSON map");
2767
+ }
2768
+ return value;
1352
2769
  } else if (type instanceof Date) {
1353
2770
  // @ts-expect-error: type
1354
2771
  return deserializeDate(srcStart, srcEnd);
@@ -1357,10 +2774,28 @@ export namespace JSON {
1357
2774
  return deserializeRaw(srcStart, srcEnd);
1358
2775
  } else if (type instanceof JSON.Value) {
1359
2776
  // @ts-expect-error: type
1360
- return deserializeArbitrary(srcStart, srcEnd, 0);
2777
+ const value = deserializeArbitrary(srcStart, srcEnd, 0);
2778
+ if (changetype<usize>(value) == 0) {
2779
+ takeProductionParseError();
2780
+ throw new Error("Incomplete JSON value");
2781
+ }
2782
+ return value;
1361
2783
  } else if (type instanceof JSON.Obj) {
1362
2784
  // @ts-expect-error: type
1363
- return deserializeObject(srcStart, srcEnd, 0);
2785
+ const value = deserializeObject(srcStart, srcEnd, 0);
2786
+ if (changetype<usize>(value) == 0) {
2787
+ takeProductionParseError();
2788
+ throw new Error("Unterminated object in JSON");
2789
+ }
2790
+ return value;
2791
+ } else if (type instanceof JSON.Arr) {
2792
+ // @ts-expect-error: type
2793
+ const value = deserializeJsonArray(srcStart, srcEnd, 0);
2794
+ if (changetype<usize>(value) == 0) {
2795
+ takeProductionParseError();
2796
+ throw new Error("Unterminated array in JSON");
2797
+ }
2798
+ return value;
1364
2799
  } else if (type instanceof JSON.Box) {
1365
2800
  // @ts-expect-error: type
1366
2801
  return new JSON.Box(
@@ -1381,21 +2816,86 @@ export namespace JSON {
1381
2816
  );
1382
2817
  }
1383
2818
  export namespace Util {
1384
- // @ts-expect-error: decorator
1385
- @inline export function isSpace(code: u16): boolean {
2819
+ /** Record a cold malformed-input path and return the zero cursor sentinel. */
2820
+ export function failProductionParse(): usize {
2821
+ return failProductionParseInternal();
2822
+ }
2823
+ export function isSpace(code: u16): boolean {
1386
2824
  return code == 0x20 || code - 9 <= 4;
1387
2825
  }
2826
+
2827
+
2828
+ @inline
2829
+ export function beginObjectTrace(
2830
+ dst: usize,
2831
+ srcStart: usize,
2832
+ typeTag: string,
2833
+ ): i32 {
2834
+ return beginObjectFieldTrace(dst, srcStart, typeTag);
2835
+ }
2836
+
2837
+
2838
+ @inline
2839
+ export function getObjectTraceMask(slot: i32): u64 {
2840
+ return objectFieldTraceMask(slot);
2841
+ }
2842
+
2843
+
2844
+ @inline
2845
+ export function getObjectTraceTier(slot: i32): u8 {
2846
+ return objectFieldTraceTier(slot);
2847
+ }
2848
+
2849
+
2850
+ @inline
2851
+ export function getObjectTraceSeparator(slot: i32): usize {
2852
+ return objectFieldTraceSeparator(slot);
2853
+ }
2854
+
2855
+
2856
+ @inline
2857
+ export function saveObjectTrace(
2858
+ slot: i32,
2859
+ present: u64,
2860
+ tier: u8,
2861
+ separator: usize = 0,
2862
+ ): void {
2863
+ recordObjectFieldTrace(slot, present, tier, separator);
2864
+ }
1388
2865
  /** Advance past JSON whitespace (space, tab, LF, VT, FF, CR). */
1389
- // @ts-expect-error: decorator
1390
- @inline export function skipWhitespace(
2866
+ @inline
2867
+ export function skipWhitespace(srcStart: usize, srcEnd: usize): usize {
2868
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
2869
+ return srcStart;
2870
+ }
2871
+ /** Advance a whitespace run using the SIMD pretty-document scanner. */
2872
+ @inline
2873
+ export function skipPrettyWhitespace(
1391
2874
  srcStart: usize,
1392
2875
  srcEnd: usize,
1393
2876
  ): usize {
1394
- while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
1395
- return srcStart;
2877
+ if (srcStart >= srcEnd) return srcStart;
2878
+ const code = load<u16>(srcStart);
2879
+ if (!isSpace(code)) return srcStart;
2880
+ if (code == 0x20) {
2881
+ srcStart += 2;
2882
+ if (srcStart >= srcEnd || !isSpace(load<u16>(srcStart)))
2883
+ return srcStart;
2884
+ }
2885
+ return skipPrettyWhitespace_SIMD(srcStart, srcEnd);
1396
2886
  }
1397
- // @ts-expect-error: decorator
1398
- @inline function scanQuotedValueEnd(srcStart: usize, srcEnd: usize): usize {
2887
+ /** Compare a bounded UTF-16 source slice with a compile-time literal. */
2888
+ @inline
2889
+ export function matchesLiteral(
2890
+ srcStart: usize,
2891
+ srcEnd: usize,
2892
+ expected: string,
2893
+ ): bool {
2894
+ const length = <usize>expected.length;
2895
+ if (srcEnd - srcStart != length << 1) return false;
2896
+ return utf16Equals(srcStart, changetype<usize>(expected), <i32>length);
2897
+ }
2898
+ function scanQuotedValueEnd(srcStart: usize, srcEnd: usize): usize {
1399
2899
  const endQuote = scanStringEnd(srcStart, srcEnd);
1400
2900
  return endQuote >= srcEnd ? 0 : endQuote + 2;
1401
2901
  }
@@ -1433,8 +2933,7 @@ export namespace JSON {
1433
2933
 
1434
2934
  return srcStart;
1435
2935
  }
1436
- // @ts-expect-error: decorator
1437
- @inline export function scanValueEnd<T = JSON.Value>(
2936
+ export function scanValueEnd<T = JSON.Value>(
1438
2937
  srcStart: usize,
1439
2938
  srcEnd: usize,
1440
2939
  ): usize {
@@ -1461,8 +2960,7 @@ export namespace JSON {
1461
2960
  return scanCompositeValueEnd(ptr, srcEnd);
1462
2961
  return scanScalarValueEnd(ptr, srcEnd);
1463
2962
  }
1464
- // @ts-expect-error: decorator
1465
- @inline export function ptrToStr(start: usize, end: usize): string {
2963
+ export function ptrToStr(start: usize, end: usize): string {
1466
2964
  const size = end - start;
1467
2965
  const out = __new(size, idof<string>());
1468
2966
  memory.copy(out, start, size);
@@ -1499,8 +2997,7 @@ export namespace JSON {
1499
2997
  * @param data - string
1500
2998
  * @returns - T
1501
2999
  */
1502
- // @ts-expect-error: inline
1503
- @inline export function parse<T>(data: string): T {
3000
+ export function parse<T>(data: string): T {
1504
3001
  bs.saveState();
1505
3002
  const result = JSON.parse<T>(data);
1506
3003
  bs.loadState();
@@ -1515,8 +3012,7 @@ export namespace JSON {
1515
3012
  * and `Date` fast paths are handled by the callers (which have buffer-reuse
1516
3013
  * optimizations); everything else routes here so the dispatch chain lives once.
1517
3014
  */
1518
- // @ts-expect-error: @inline is a valid decorator
1519
- @inline function serializeReference<T>(data: T): void {
3015
+ function serializeReference<T>(data: T): void {
1520
3016
  if (data instanceof Array) {
1521
3017
  // @ts-expect-error
1522
3018
  serializeArray(changetype<nonnull<T>>(data));
@@ -1563,6 +3059,8 @@ export namespace JSON {
1563
3059
  serializeArbitrary(data);
1564
3060
  } else if (data instanceof JSON.Obj) {
1565
3061
  serializeObject(data);
3062
+ } else if (data instanceof JSON.Arr) {
3063
+ serializeJsonArray(data);
1566
3064
  } else if (data instanceof JSON.Box) {
1567
3065
  JSON.__serialize(data.value);
1568
3066
  } else {
@@ -1580,12 +3078,11 @@ export enum JSONMode {
1580
3078
  NAIVE = 2,
1581
3079
  }
1582
3080
 
1583
- // @ts-expect-error: decorator
1584
- @inline function parseBox<T>(data: string, ty: T): T {
3081
+ function parseBox<T>(data: string, ty: T): T {
1585
3082
  return JSON.parse<T>(data);
1586
3083
  }
1587
- // @ts-expect-error: inline
1588
- @inline function deserializeBox<T>(
3084
+
3085
+ function deserializeBox<T>(
1589
3086
  srcStart: usize,
1590
3087
  srcEnd: usize,
1591
3088
  dst: usize,
@@ -1594,16 +3091,13 @@ export enum JSONMode {
1594
3091
  return JSON.__deserialize<T>(srcStart, srcEnd, dst);
1595
3092
  }
1596
3093
 
1597
- // @ts-expect-error: inline
1598
- @inline export function toRaw(data: string): JSON.Raw {
3094
+ export function toRaw(data: string): JSON.Raw {
1599
3095
  return new JSON.Raw(data);
1600
3096
  }
1601
- // @ts-expect-error: inline
1602
- @inline export function fromRaw(data: JSON.Raw): string {
3097
+ export function fromRaw(data: JSON.Raw): string {
1603
3098
  return data.data;
1604
3099
  }
1605
3100
 
1606
- // @ts-expect-error: inline
1607
- @inline export function toBox<T>(data: T): JSON.Box<T> {
3101
+ export function toBox<T>(data: T): JSON.Box<T> {
1608
3102
  return new JSON.Box<T>(data);
1609
3103
  }