json-as 1.5.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 (66) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/README.md +42 -0
  3. package/assembly/deserialize/error.ts +21 -0
  4. package/assembly/deserialize/index/arbitrary.ts +29 -8
  5. package/assembly/deserialize/index/array.ts +2 -1
  6. package/assembly/deserialize/index/bool.ts +1 -1
  7. package/assembly/deserialize/index/object.ts +3 -0
  8. package/assembly/deserialize/index/string.ts +37 -0
  9. package/assembly/deserialize/index.ts +0 -1
  10. package/assembly/deserialize/naive/array/arbitrary.ts +3 -2
  11. package/assembly/deserialize/naive/array/array.ts +1 -0
  12. package/assembly/deserialize/naive/array/bool.ts +21 -11
  13. package/assembly/deserialize/naive/array/box.ts +13 -33
  14. package/assembly/deserialize/naive/array/float.ts +23 -13
  15. package/assembly/deserialize/naive/array/integer.ts +33 -21
  16. package/assembly/deserialize/naive/array/object.ts +1 -0
  17. package/assembly/deserialize/naive/array/string.ts +29 -16
  18. package/assembly/deserialize/naive/bool.ts +17 -5
  19. package/assembly/deserialize/naive/float.ts +1 -2
  20. package/assembly/deserialize/naive/map.ts +339 -39
  21. package/assembly/deserialize/naive/object.ts +122 -65
  22. package/assembly/deserialize/naive/raw.ts +13 -1
  23. package/assembly/deserialize/naive/set.ts +0 -1
  24. package/assembly/deserialize/naive/staticarray.ts +3 -2
  25. package/assembly/deserialize/naive/string.ts +41 -52
  26. package/assembly/deserialize/parseMode.ts +216 -0
  27. package/assembly/deserialize/simd/array/integer.ts +6 -3
  28. package/assembly/deserialize/simd/float.ts +35 -29
  29. package/assembly/deserialize/simd/string.ts +79 -17
  30. package/assembly/deserialize/string-validation.ts +34 -0
  31. package/assembly/deserialize/swar/array/array.ts +63 -13
  32. package/assembly/deserialize/swar/array/bool.ts +14 -3
  33. package/assembly/deserialize/swar/array/float.ts +394 -13
  34. package/assembly/deserialize/swar/array/generic.ts +12 -2
  35. package/assembly/deserialize/swar/array/integer.ts +22 -62
  36. package/assembly/deserialize/swar/array/object.ts +11 -2
  37. package/assembly/deserialize/swar/array/shared.ts +1 -1
  38. package/assembly/deserialize/swar/array/string.ts +34 -5
  39. package/assembly/deserialize/swar/array/struct.ts +109 -15
  40. package/assembly/deserialize/swar/float.ts +19 -12
  41. package/assembly/deserialize/swar/string.ts +70 -111
  42. package/assembly/index.d.ts +6 -0
  43. package/assembly/index.ts +411 -33
  44. package/assembly/serialize/index/bool.ts +1 -1
  45. package/assembly/serialize/index/float.ts +1 -5
  46. package/assembly/serialize/index/integer.ts +1 -1
  47. package/assembly/serialize/index/object.ts +17 -0
  48. package/assembly/serialize/naive/array.ts +64 -0
  49. package/assembly/serialize/naive/float.ts +0 -5
  50. package/assembly/serialize/naive/map.ts +37 -0
  51. package/assembly/serialize/naive/set.ts +0 -1
  52. package/assembly/serialize/naive/staticarray.ts +0 -1
  53. package/assembly/serialize/naive/string.ts +0 -6
  54. package/assembly/serialize/naive/typedarray.ts +0 -1
  55. package/assembly/util/eisel-lemire.ts +179 -0
  56. package/assembly/util/prettyWhitespaceSimd.ts +69 -0
  57. package/assembly/util/scanValueEndSimd.ts +59 -36
  58. package/assembly/util/validateJson.ts +338 -0
  59. package/lib/as-bs.ts +28 -6
  60. package/package.json +13 -3
  61. package/transform/lib/index.d.ts +6 -0
  62. package/transform/lib/index.js +770 -111
  63. package/transform/lib/types.d.ts +1 -0
  64. package/transform/lib/types.js +3 -0
  65. package/assembly/deserialize/index/struct.ts +0 -1
  66. package/assembly/deserialize/naive/struct.ts +0 -21
@@ -1,4 +1,3 @@
1
- import { JSON } from "../../..";
2
1
  import {
3
2
  COMMA,
4
3
  BRACKET_LEFT,
@@ -7,6 +6,8 @@ import {
7
6
  QUOTE,
8
7
  } from "../../../custom/chars";
9
8
  import { isSpace, scanStringEnd } from "../../../util";
9
+ import { markProductionParseError } from "../../error";
10
+ import { deserializeString } from "../../index/string";
10
11
 
11
12
  /**
12
13
  * Strict string-array deserializer (`string[]` / `(string | null)[]`).
@@ -14,8 +15,8 @@ import { isSpace, scanStringEnd } from "../../../util";
14
15
  * Enforces RFC 8259 array structure: `[`-framed, single-comma separated, no
15
16
  * leading / trailing / doubled commas, and each element must be a quoted string
16
17
  * (or the literal `null` for a nullable element type). Element contents are
17
- * validated by `deserializeString_NAIVE` (via `JSON.__deserialize`). Throws on
18
- * any deviation.
18
+ * validated by the selected whole-string decoder. Returns the shared failure
19
+ * sentinel on any deviation so JSON.parse can throw at its public boundary.
19
20
  */
20
21
  export function deserializeStringArray_NAIVE(
21
22
  srcStart: usize,
@@ -27,12 +28,15 @@ export function deserializeStringArray_NAIVE(
27
28
  );
28
29
  out.length = 0; // dst may arrive pre-sized; re-parse from empty via push
29
30
 
30
- while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
31
31
  while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
32
- if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT)
33
- throw new Error("Invalid JSON array: expected '['");
34
- if (load<u16>(srcEnd - 2) != BRACKET_RIGHT)
35
- throw new Error("Invalid JSON array: expected ']'");
32
+ if (
33
+ srcStart >= srcEnd ||
34
+ load<u16>(srcStart) != BRACKET_LEFT ||
35
+ load<u16>(srcEnd - 2) != BRACKET_RIGHT
36
+ ) {
37
+ markProductionParseError();
38
+ return changetype<string[]>(0);
39
+ }
36
40
  srcStart += 2; // past '['
37
41
  srcEnd -= 2; // before ']'
38
42
 
@@ -43,26 +47,35 @@ export function deserializeStringArray_NAIVE(
43
47
  const code = load<u16>(srcStart);
44
48
  if (code == QUOTE) {
45
49
  const closing = scanStringEnd(srcStart, srcEnd);
46
- if (closing >= srcEnd)
47
- throw new Error("Invalid JSON array: unterminated string");
48
- out.push(JSON.__deserialize<string>(srcStart, closing + 2));
50
+ if (closing >= srcEnd) {
51
+ markProductionParseError();
52
+ return changetype<string[]>(0);
53
+ }
54
+ const value = deserializeString(srcStart, closing + 2);
55
+ if (changetype<usize>(value) == 0) return changetype<string[]>(0);
56
+ out.push(value);
49
57
  srcStart = closing + 2;
50
58
  } else if (srcStart + 8 <= srcEnd && load<u64>(srcStart) == NULL_WORD_U64) {
51
59
  // `(string | null)[]` element
52
60
  out.push(changetype<string>(0));
53
61
  srcStart += 8;
54
62
  } else {
55
- throw new Error("Invalid JSON array: expected string");
63
+ markProductionParseError();
64
+ return changetype<string[]>(0);
56
65
  }
57
66
 
58
67
  while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
59
68
  if (srcStart >= srcEnd) break;
60
- if (load<u16>(srcStart) != COMMA)
61
- throw new Error("Invalid JSON array: expected ',' or ']'");
69
+ if (load<u16>(srcStart) != COMMA) {
70
+ markProductionParseError();
71
+ return changetype<string[]>(0);
72
+ }
62
73
  srcStart += 2;
63
74
  while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
64
- if (srcStart >= srcEnd)
65
- throw new Error("Invalid JSON array: trailing comma");
75
+ if (srcStart >= srcEnd) {
76
+ markProductionParseError();
77
+ return changetype<string[]>(0);
78
+ }
66
79
  }
67
80
 
68
81
  return out;
@@ -1,7 +1,19 @@
1
+ // 0 = invalid, 1 = false, 2 = true. The status form lets public dispatchers
2
+ // raise malformed-input errors at their catchable boundary without rescanning.
3
+ export function deserializeBooleanCode(srcStart: usize, srcEnd: usize): u32 {
4
+ const length = srcEnd - srcStart;
5
+ if (length >= 8 && load<u64>(srcStart) == 28429475166421108) return 2;
6
+ if (
7
+ length >= 10 &&
8
+ load<u64>(srcStart) == 32370086184550502 &&
9
+ load<u16>(srcStart, 8) == 101
10
+ )
11
+ return 1;
12
+ return 0;
13
+ }
14
+
1
15
  export function deserializeBoolean(srcStart: usize, srcEnd: usize): boolean {
2
- const block = load<u64>(srcStart);
3
- if (block == 28429475166421108) return true;
4
- else if (block == 32370086184550502 && load<u16>(srcStart, 8) == 101)
5
- return false;
6
- return false; //throw new Error(`Expected to find boolean, but found "${data.slice(0, 100)}" instead!`);
16
+ const code = deserializeBooleanCode(srcStart, srcEnd);
17
+ if (code != 0) return code == 2;
18
+ throw new Error("Expected 'true' or 'false' in JSON");
7
19
  }
@@ -9,7 +9,6 @@ import { isSpace } from "../../util";
9
9
  // numbers like `0e`, `-01`, `1.`, `2.e3`, `0x42`.
10
10
  function validateJSONNumber(srcStart: usize, srcEnd: usize): void {
11
11
  let ptr = srcStart;
12
- while (ptr < srcEnd && isSpace(load<u16>(ptr))) ptr += 2;
13
12
  let end = srcEnd;
14
13
  while (end > ptr && isSpace(load<u16>(end - 2))) end -= 2;
15
14
  if (ptr >= end) throw new Error("Invalid JSON number: empty");
@@ -62,7 +61,7 @@ function validateJSONNumber(srcStart: usize, srcEnd: usize): void {
62
61
  }
63
62
 
64
63
  export function deserializeFloat_NAIVE<T>(srcStart: usize, srcEnd: usize): T {
65
- validateJSONNumber(srcStart, srcEnd);
64
+ if (JSON_STRICT) validateJSONNumber(srcStart, srcEnd);
66
65
  // @ts-ignore
67
66
  const type: T = 0;
68
67
  // @ts-ignore
@@ -6,43 +6,127 @@ import {
6
6
  BRACE_RIGHT,
7
7
  COLON,
8
8
  } from "../../custom/chars";
9
- import { isSpace, scanStringEnd } from "../../util";
9
+ import { isSpace } from "../../util";
10
10
  import { scanValueEnd } from "../../util/scanValueEnd";
11
11
  import { lastValueEnd, parseValue } from "./object";
12
+ import { deserializeRaw } from "./raw";
13
+ import { isPrettyParse } from "../parseMode";
12
14
 
13
- function deserializeMapKey<T>(start: usize, end: usize): T {
15
+
16
+ @unmanaged
17
+ class ReusableMapEntry<K, V> {
18
+ key: K;
19
+ value: V;
20
+ taggedNext: usize;
21
+ }
22
+
23
+
24
+ @inline
25
+ function reusableMapEntrySize<K, V>(): usize {
26
+ const maxKV = sizeof<K>() > sizeof<V>() ? sizeof<K>() : sizeof<V>();
27
+ const align = (maxKV > sizeof<usize>() ? maxKV : sizeof<usize>()) - 1;
28
+ return (offsetof<ReusableMapEntry<K, V>>() + align) & ~align;
29
+ }
30
+
31
+
32
+ @inline
33
+ function skipMapWhitespace(srcStart: usize, srcEnd: usize): usize {
34
+ // Root dynamic maps produced with a two-space JSON.stringify indent repeat
35
+ // LF + two spaces + quote for every key. Validate that fixed shape in two
36
+ // loads, retaining the scalar grammar path for all other whitespace.
37
+ if (
38
+ ASC_FEATURE_SIMD &&
39
+ srcStart + 8 <= srcEnd &&
40
+ load<u16>(srcStart) == 10 &&
41
+ load<u32>(srcStart, 2) == 0x0020_0020 &&
42
+ load<u16>(srcStart, 6) == QUOTE
43
+ )
44
+ return srcStart + 6;
45
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
46
+ return srcStart;
47
+ }
48
+
49
+ function rawMapKeyEquals(key: string, start: usize, end: usize): bool {
50
+ const byteLength = end - start;
51
+ if (<usize>(key.length << 1) != byteLength) return false;
52
+ const keyStart = changetype<usize>(key);
53
+ let offset: usize = 0;
54
+ while (offset + 8 <= byteLength) {
55
+ if (load<u64>(keyStart + offset) != load<u64>(start + offset)) return false;
56
+ offset += 8;
57
+ }
58
+ while (offset < byteLength) {
59
+ if (load<u16>(keyStart + offset) != load<u16>(start + offset)) return false;
60
+ offset += 2;
61
+ }
62
+ return true;
63
+ }
64
+
65
+ function deserializeMapKey<T>(
66
+ start: usize,
67
+ end: usize,
68
+ reusableKey: string = "",
69
+ canReuse: bool = false,
70
+ ): T {
71
+ if (isString<T>() && canReuse && rawMapKeyEquals(reusableKey, start, end)) {
72
+ return changetype<T>(reusableKey);
73
+ }
14
74
  // @ts-expect-error: exists
15
75
  const keyText = JSON.__deserialize<string>(start - 2, end + 2);
16
76
  if (isString<T>()) return changetype<T>(keyText);
17
77
  return JSON.parse<T>(keyText);
18
78
  }
19
79
 
80
+ function removeStaleMapKeys<T extends Map<any, any>>(
81
+ out: T,
82
+ previousKeys: Array<indexof<T>>,
83
+ seenKeys: Set<indexof<T>>,
84
+ parsedKeyCount: i32,
85
+ ): void {
86
+ if (changetype<usize>(previousKeys) == 0) return;
87
+ if (changetype<usize>(seenKeys) == 0 && parsedKeyCount == previousKeys.length)
88
+ return;
89
+
90
+ // The common case preserves key order and needs no Set. If the new object
91
+ // changed shape/order (or contains duplicates), materialize the Set only on
92
+ // that rare path. Keys before the first mismatch matched by construction.
93
+ if (changetype<usize>(seenKeys) == 0) {
94
+ seenKeys = new Set<indexof<T>>();
95
+ const matchedPrefix = min<i32>(parsedKeyCount, previousKeys.length);
96
+ for (let i = 0; i < matchedPrefix; i++) {
97
+ seenKeys.add(unchecked(previousKeys[i]));
98
+ }
99
+ }
100
+ for (let i = 0; i < previousKeys.length; i++) {
101
+ const key = unchecked(previousKeys[i]);
102
+ if (!seenKeys.has(key)) changetype<nonnull<T>>(out).delete(key);
103
+ }
104
+ }
105
+
20
106
  export function deserializeMap<T extends Map<any, any>>(
21
107
  srcStart: usize,
22
108
  srcEnd: usize,
23
109
  dst: usize,
24
110
  ): T {
111
+ const reuseExisting = dst != 0;
25
112
  const out = changetype<nonnull<T>>(
26
113
  dst || changetype<usize>(instantiate<T>()),
27
114
  );
28
115
 
29
116
  while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
30
117
 
31
- if (srcStart - srcEnd == 0)
32
- throw new Error("Input string had zero length or was all whitespace");
33
- if (load<u16>(srcStart) != BRACE_LEFT)
34
- throw new Error(
35
- "Expected '{' at start of object at position " +
36
- (srcEnd - srcStart).toString(),
37
- );
38
- if (load<u16>(srcEnd - 2) != BRACE_RIGHT)
39
- throw new Error(
40
- "Expected '}' at end of object at position " +
41
- (srcEnd - srcStart).toString(),
42
- );
118
+ if (srcStart - srcEnd == 0) return changetype<T>(0);
119
+ if (load<u16>(srcStart) != BRACE_LEFT) return changetype<T>(0);
120
+ if (load<u16>(srcEnd - 2) != BRACE_RIGHT) return changetype<T>(0);
43
121
 
44
- deserializeMapBody<T>(srcStart, srcEnd, changetype<T>(out));
45
- return changetype<T>(out);
122
+ return deserializeMapBody<T>(
123
+ srcStart,
124
+ srcEnd,
125
+ changetype<T>(out),
126
+ reuseExisting,
127
+ ) != 0
128
+ ? changetype<T>(out)
129
+ : changetype<T>(0);
46
130
  }
47
131
 
48
132
  /**
@@ -55,66 +139,285 @@ export function deserializeMapBody<T extends Map<any, any>>(
55
139
  srcStart: usize,
56
140
  srcEnd: usize,
57
141
  out: T,
142
+ reuseExisting: bool = false,
58
143
  ): usize {
144
+ const pretty = ASC_FEATURE_SIMD && isPrettyParse();
59
145
  let arbitraryValue = false;
146
+ let rawValue = false;
60
147
  if (isManaged<valueof<T>>() || isReference<valueof<T>>()) {
61
148
  // @ts-ignore: instanceof on the (reference) value type
62
149
  arbitraryValue = changetype<nonnull<valueof<T>>>(0) instanceof JSON.Value;
150
+ // @ts-ignore: instanceof on the (reference) value type
151
+ rawValue = changetype<nonnull<valueof<T>>>(0) instanceof JSON.Raw;
63
152
  }
64
153
 
65
- if (srcStart >= srcEnd || load<u16>(srcStart) != BRACE_LEFT)
66
- throw new Error("Failed to parse JSON!");
154
+ let previousKeys = changetype<Array<indexof<T>>>(0);
155
+ const previousEntries = reuseExisting
156
+ ? load<ArrayBuffer>(
157
+ changetype<usize>(out),
158
+ offsetof<Map<indexof<T>, valueof<T>>>("entries"),
159
+ )
160
+ : changetype<ArrayBuffer>(0);
161
+ const previousEntrySize = reusableMapEntrySize<indexof<T>, valueof<T>>();
162
+ let previousEntry = changetype<usize>(previousEntries);
163
+ const previousEntryEnd = reuseExisting
164
+ ? previousEntry +
165
+ <usize>(
166
+ load<i32>(
167
+ changetype<usize>(out),
168
+ offsetof<Map<indexof<T>, valueof<T>>>("entriesOffset"),
169
+ )
170
+ ) *
171
+ previousEntrySize
172
+ : 0;
173
+ const previousCount = reuseExisting ? changetype<nonnull<T>>(out).size : 0;
174
+ while (
175
+ previousEntry < previousEntryEnd &&
176
+ (load<usize>(
177
+ previousEntry,
178
+ offsetof<ReusableMapEntry<indexof<T>, valueof<T>>>("taggedNext"),
179
+ ) &
180
+ 1) !=
181
+ 0
182
+ )
183
+ previousEntry += previousEntrySize;
184
+ let seenKeys = changetype<Set<indexof<T>>>(0);
185
+ let parsedKeyCount = 0;
186
+
187
+ if (srcStart >= srcEnd || load<u16>(srcStart) != BRACE_LEFT) return 0;
67
188
  srcStart += 2;
68
- while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
69
- if (srcStart >= srcEnd) throw new Error("Failed to parse JSON!");
70
- if (load<u16>(srcStart) == BRACE_RIGHT) return srcStart + 2;
189
+ if (srcStart < srcEnd && load<u16>(srcStart) != QUOTE)
190
+ srcStart = skipMapWhitespace(srcStart, srcEnd);
191
+ if (srcStart >= srcEnd) return 0;
192
+ if (load<u16>(srcStart) == BRACE_RIGHT) {
193
+ if (reuseExisting) changetype<nonnull<T>>(out).clear();
194
+ return srcStart + 2;
195
+ }
71
196
 
72
197
  while (srcStart < srcEnd) {
73
- while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
74
198
  if (load<u16>(srcStart) != QUOTE) break;
75
199
 
76
200
  const keyStart = srcStart + 2;
77
- const keyEnd = scanStringEnd(srcStart, srcEnd);
78
- if (keyEnd >= srcEnd) break;
201
+ const keyValueEnd = JSON.Util.scanValueEnd<string>(srcStart, srcEnd);
202
+ if (!keyValueEnd) break;
203
+ const keyEnd = keyValueEnd - 2;
79
204
 
80
205
  srcStart = keyEnd + 2;
81
- while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
206
+ if (srcStart < srcEnd && load<u16>(srcStart) != COLON)
207
+ srcStart = skipMapWhitespace(srcStart, srcEnd);
82
208
  if (srcStart >= srcEnd || load<u16>(srcStart) != COLON) break;
83
209
  srcStart += 2;
84
- while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
210
+ if (srcStart < srcEnd && isSpace(load<u16>(srcStart)))
211
+ srcStart = skipMapWhitespace(srcStart, srcEnd);
212
+
213
+ let reusableKey = "";
214
+ const canReuseKey =
215
+ isString<indexof<T>>() &&
216
+ reuseExisting &&
217
+ previousEntry < previousEntryEnd;
218
+ if (canReuseKey) {
219
+ reusableKey = changetype<string>(
220
+ load<indexof<T>>(
221
+ previousEntry,
222
+ offsetof<ReusableMapEntry<indexof<T>, valueof<T>>>("key"),
223
+ ),
224
+ );
225
+ }
226
+ const key = deserializeMapKey<indexof<T>>(
227
+ keyStart,
228
+ keyEnd,
229
+ reusableKey,
230
+ canReuseKey,
231
+ );
232
+ const matchedPreviousKey =
233
+ reuseExisting &&
234
+ previousEntry < previousEntryEnd &&
235
+ key ==
236
+ load<indexof<T>>(
237
+ previousEntry,
238
+ offsetof<ReusableMapEntry<indexof<T>, valueof<T>>>("key"),
239
+ );
240
+ if (reuseExisting) {
241
+ if (changetype<usize>(seenKeys) == 0 && !matchedPreviousKey) {
242
+ previousKeys = changetype<nonnull<T>>(out).keys();
243
+ seenKeys = new Set<indexof<T>>();
244
+ for (let i = 0; i < parsedKeyCount; i++) {
245
+ seenKeys.add(unchecked(previousKeys[i]));
246
+ }
247
+ }
248
+ if (changetype<usize>(seenKeys) != 0) seenKeys.add(key);
249
+ parsedKeyCount++;
250
+ }
85
251
 
86
252
  if (isReference<valueof<T>>() && arbitraryValue) {
87
253
  const val = parseValue(srcStart, srcEnd);
254
+ const next = lastValueEnd();
255
+ if (!next) break;
88
256
  // @ts-ignore: type - valueof<T> is JSON.Value in this branch
89
257
  changetype<nonnull<T>>(out).set(
90
- deserializeMapKey<indexof<T>>(keyStart, keyEnd),
258
+ key,
91
259
  changetype<valueof<T>>(changetype<usize>(val)),
92
260
  );
93
- srcStart = lastValueEnd();
261
+ srcStart = next;
262
+ } else if (isReference<valueof<T>>() && rawValue) {
263
+ const valueEnd = scanValueEnd(srcStart, srcEnd);
264
+ if (!valueEnd || valueEnd <= srcStart) break;
265
+ let raw: JSON.Raw | null = null;
266
+ if (changetype<nonnull<T>>(out).has(key)) {
267
+ raw = changetype<JSON.Raw>(
268
+ changetype<usize>(changetype<nonnull<T>>(out).get(key)),
269
+ );
270
+ }
271
+ const value = deserializeRaw(srcStart, valueEnd, changetype<usize>(raw));
272
+ // @ts-ignore: valueof<T> is JSON.Raw in this branch
273
+ changetype<nonnull<T>>(out).set(
274
+ key,
275
+ changetype<valueof<T>>(changetype<usize>(value)),
276
+ );
277
+ srcStart = valueEnd;
278
+ } else if (isManaged<valueof<T>>() || isReference<valueof<T>>()) {
279
+ const valueType = changetype<nonnull<valueof<T>>>(0);
280
+ if (
281
+ // @ts-ignore: supplied by the json-as transform
282
+ isDefined(valueType.__DESERIALIZE_FAST) ||
283
+ // @ts-ignore: supplied by the json-as transform
284
+ isDefined(valueType.__DESERIALIZE_SLOW)
285
+ ) {
286
+ // Dynamic-key object maps (GSOC/CITM-style corpora) previously scanned
287
+ // every complete value to discover its end, then allocated and parsed
288
+ // it in a second pass. Generated struct parsers already return the
289
+ // cursor after `}`, so drive them directly and reuse a mapped instance
290
+ // when the key was present in the destination map.
291
+ let value = changetype<valueof<T>>(0);
292
+ if (matchedPreviousKey) {
293
+ value = load<valueof<T>>(
294
+ previousEntry,
295
+ offsetof<ReusableMapEntry<indexof<T>, valueof<T>>>("value"),
296
+ );
297
+ } else if (changetype<nonnull<T>>(out).has(key)) {
298
+ value = changetype<nonnull<T>>(out).get(key);
299
+ }
300
+ const reusedMappedValue =
301
+ matchedPreviousKey && changetype<usize>(value) != 0;
302
+ if (changetype<usize>(value) == 0) {
303
+ value = changetype<valueof<T>>(
304
+ __new(offsetof<nonnull<valueof<T>>>(), idof<nonnull<valueof<T>>>()),
305
+ );
306
+ // @ts-ignore: supplied by the json-as transform
307
+ if (isDefined(valueType.__INITIALIZE)) {
308
+ // @ts-ignore: supplied by the json-as transform
309
+ changetype<nonnull<valueof<T>>>(value).__INITIALIZE();
310
+ }
311
+ }
312
+
313
+ const valueStart = srcStart;
314
+ let next: usize = 0;
315
+ // @ts-ignore: supplied by the json-as transform
316
+ if (isDefined(valueType.__DESERIALIZE_FAST)) {
317
+ // @ts-ignore: supplied by the json-as transform
318
+ if (pretty && isDefined(valueType.__DESERIALIZE_FAST_PRETTY)) {
319
+ // @ts-ignore: supplied by the json-as transform
320
+ next = changetype<nonnull<valueof<T>>>(
321
+ value,
322
+ ).__DESERIALIZE_FAST_PRETTY(srcStart, srcEnd, value);
323
+ } else {
324
+ // @ts-ignore: supplied by the json-as transform
325
+ next = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_FAST(
326
+ srcStart,
327
+ srcEnd,
328
+ value,
329
+ );
330
+ }
331
+ }
332
+ if (!next) {
333
+ const valueEnd = scanValueEnd(valueStart, srcEnd);
334
+ if (!valueEnd || valueEnd <= valueStart) break;
335
+ // A failed FAST attempt may have partially overwritten fields.
336
+ // @ts-ignore: supplied by the json-as transform
337
+ if (isDefined(valueType.__INITIALIZE)) {
338
+ // @ts-ignore: supplied by the json-as transform
339
+ changetype<nonnull<valueof<T>>>(value).__INITIALIZE();
340
+ }
341
+ // @ts-ignore: supplied by the json-as transform
342
+ changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_SLOW(
343
+ valueStart,
344
+ valueEnd,
345
+ value,
346
+ );
347
+ next = valueEnd;
348
+ }
349
+
350
+ // Stable-order re-parses mutate the same mapped object in place. Avoid
351
+ // hashing the key and re-linking the identical reference on every
352
+ // entry; shape/order changes still take the ordinary Map path.
353
+ if (!reusedMappedValue) changetype<nonnull<T>>(out).set(key, value);
354
+ srcStart = next;
355
+ } else {
356
+ const valueEnd = scanValueEnd(srcStart, srcEnd);
357
+ if (!valueEnd || valueEnd <= srcStart) break;
358
+ // @ts-ignore: type
359
+ changetype<nonnull<T>>(out).set(
360
+ key,
361
+ JSON.__deserialize<valueof<T>>(srcStart, valueEnd),
362
+ );
363
+ srcStart = valueEnd;
364
+ }
94
365
  } else {
95
366
  const valueEnd = scanValueEnd(srcStart, srcEnd);
96
367
  if (!valueEnd || valueEnd <= srcStart) break;
97
368
  // @ts-ignore: type
98
369
  changetype<nonnull<T>>(out).set(
99
- deserializeMapKey<indexof<T>>(keyStart, keyEnd),
370
+ key,
100
371
  JSON.__deserialize<valueof<T>>(srcStart, valueEnd),
101
372
  );
102
373
  srcStart = valueEnd;
103
374
  }
104
375
 
105
- while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
376
+ if (matchedPreviousKey) {
377
+ previousEntry += previousEntrySize;
378
+ while (
379
+ previousEntry < previousEntryEnd &&
380
+ (load<usize>(
381
+ previousEntry,
382
+ offsetof<ReusableMapEntry<indexof<T>, valueof<T>>>("taggedNext"),
383
+ ) &
384
+ 1) !=
385
+ 0
386
+ )
387
+ previousEntry += previousEntrySize;
388
+ } else {
389
+ // Once insertion order diverges, ordinary Map lookup is the safe path
390
+ // for all remaining values. `seenKeys` tracks stale-key cleanup.
391
+ previousEntry = previousEntryEnd;
392
+ }
393
+
106
394
  if (srcStart >= srcEnd) break;
107
- const code = load<u16>(srcStart);
395
+ let code = load<u16>(srcStart);
396
+ if (code != COMMA && code != BRACE_RIGHT) {
397
+ srcStart = skipMapWhitespace(srcStart, srcEnd);
398
+ if (srcStart >= srcEnd) break;
399
+ code = load<u16>(srcStart);
400
+ }
108
401
  if (code == COMMA) {
109
402
  srcStart += 2;
110
- while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
403
+ if (srcStart < srcEnd && load<u16>(srcStart) != QUOTE)
404
+ srcStart = skipMapWhitespace(srcStart, srcEnd);
111
405
  continue;
112
406
  }
113
- if (code == BRACE_RIGHT) return srcStart + 2;
407
+ if (code == BRACE_RIGHT) {
408
+ if (
409
+ reuseExisting &&
410
+ changetype<usize>(previousKeys) == 0 &&
411
+ parsedKeyCount != previousCount
412
+ )
413
+ previousKeys = changetype<nonnull<T>>(out).keys();
414
+ removeStaleMapKeys<T>(out, previousKeys, seenKeys, parsedKeyCount);
415
+ return srcStart + 2;
416
+ }
114
417
  break;
115
418
  }
116
419
 
117
- throw new Error("Failed to parse JSON!");
420
+ return 0;
118
421
  }
119
422
 
120
423
  export function deserializeMapField<T extends Map<any, any>>(
@@ -125,13 +428,10 @@ export function deserializeMapField<T extends Map<any, any>>(
125
428
  ): usize {
126
429
  const fieldPtr = dstObj + dstOffset;
127
430
  let out = load<T>(fieldPtr);
431
+ const reuseExisting = changetype<usize>(out) != 0;
128
432
  if (!changetype<usize>(out)) {
129
433
  out = changetype<T>(instantiate<T>());
130
434
  store<T>(fieldPtr, out);
131
- } else {
132
- // Reusing an existing field map - clear it before repopulating. Fresh maps
133
- // (deserializeMap / deserializeMapArray) skip this.
134
- changetype<nonnull<T>>(out).clear();
135
435
  }
136
- return deserializeMapBody<T>(srcStart, srcEnd, out);
436
+ return deserializeMapBody<T>(srcStart, srcEnd, out, reuseExisting);
137
437
  }