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
@@ -1,21 +1,77 @@
1
1
  import { JSON } from "../..";
2
2
  import { bs } from "../../../lib/as-bs";
3
- import { serializeArray } from "./array";
3
+ import { bytes } from "../../util/bytes";
4
+ import { QUOTE } from "../../custom/chars";
4
5
  import { serializeBool } from "./bool";
5
6
  import { serializeFloat32, serializeFloat64 } from "./float";
6
7
  import { serializeInteger } from "./integer";
8
+ import { serializeJsonArray } from "./jsonarray";
7
9
  import { serializeMap } from "./map";
8
10
  import { serializeObject } from "./object";
11
+ import { serializeRaw } from "../naive/raw";
9
12
  import { serializeString } from "./string";
10
13
  import { serializeDynamic } from "./typedarray";
11
14
 
15
+ // True if any code unit would need JSON-escaping on serialize: a quote,
16
+ // backslash, control char (< 0x20), or a surrogate (handled conservatively -
17
+ // even valid pairs route to the full path, since they can't be bulk-copied as a
18
+ // plain run). A clean string is none of these, so it emits as a verbatim memcpy.
19
+ // @ts-ignore: decorator
20
+ @inline function stringNeedsEscape(src: string): bool {
21
+ let ptr = changetype<usize>(src);
22
+ const end = ptr + <usize>bytes(src);
23
+ while (ptr < end) {
24
+ const code = load<u16>(ptr);
25
+ if (
26
+ code == 0x22 ||
27
+ code == 0x5c ||
28
+ code < 0x20 ||
29
+ (code >= 0xd800 && code <= 0xdfff)
30
+ )
31
+ return true;
32
+ ptr += 2;
33
+ }
34
+ return false;
35
+ }
36
+
37
+ // Fast path for a string already known to need no escaping: quote + a single
38
+ // bulk copy of the UTF-16 bytes + quote. Skips the per-char escape scan that
39
+ // serializeString would otherwise do (and its trailing second pass).
40
+ function serializeStringClean(src: string): void {
41
+ const size = <usize>bytes(src);
42
+ bs.proposeSize(size + 4);
43
+ store<u16>(bs.offset, QUOTE);
44
+ bs.offset += 2;
45
+ memory.copy(bs.offset, changetype<usize>(src), size);
46
+ bs.offset += size;
47
+ store<u16>(bs.offset, QUOTE);
48
+ bs.offset += 2;
49
+ }
50
+
12
51
  export function serializeArbitrary(src: JSON.Value): void {
52
+ // Verbatim passthrough: an untouched (still-deferred) value emits its original
53
+ // source bytes directly - no materialization, no re-encoding. Peek the slice
54
+ // without reading `src.type` (which would force materialization).
55
+ const lz = src.__lazySlice();
56
+ if (lz != 0) {
57
+ const start = <usize>(lz >>> 32);
58
+ const size = <usize>(<u32>lz) - start;
59
+ bs.proposeSize(size);
60
+ memory.copy(bs.offset, start, size);
61
+ bs.offset += size;
62
+ return;
63
+ }
13
64
  switch (src.type) {
14
65
  case JSON.Types.Null:
15
66
  bs.proposeSize(8);
16
67
  store<u64>(bs.offset, 30399761348886638);
17
68
  bs.offset += 8;
18
69
  break;
70
+ case JSON.Types.Raw:
71
+ // A materialized JSON.Raw value (e.g. set into a JSON.Obj/Arr) emits its
72
+ // pre-formatted bytes as-is - without this it fell into the struct default.
73
+ serializeRaw(src.get<JSON.Raw>());
74
+ break;
19
75
  case JSON.Types.U8:
20
76
  serializeInteger<u8>(src.get<u8>());
21
77
  break;
@@ -46,14 +102,24 @@ export function serializeArbitrary(src: JSON.Value): void {
46
102
  case JSON.Types.F64:
47
103
  serializeFloat64(src.get<f64>());
48
104
  break;
49
- case JSON.Types.String:
50
- serializeString(src.get<string>());
105
+ case JSON.Types.String: {
106
+ const str = src.get<string>();
107
+ // Reuse the cached escape class; classify once on first serialize. Clean
108
+ // strings (the common case) then emit as a single memcpy on every reuse.
109
+ let cls = src.__strClass();
110
+ if (cls == 0) {
111
+ cls = stringNeedsEscape(str) ? 2 : 1;
112
+ src.__setStrClass(cls);
113
+ }
114
+ if (cls == 1) serializeStringClean(str);
115
+ else serializeString(str);
51
116
  break;
117
+ }
52
118
  case JSON.Types.Bool:
53
119
  serializeBool(src.get<bool>());
54
120
  break;
55
121
  case JSON.Types.Array:
56
- serializeArray(src.get<JSON.Value[]>());
122
+ serializeJsonArray(src.get<JSON.Arr>());
57
123
  break;
58
124
  case JSON.Types.Object:
59
125
  serializeObject(src.get<JSON.Obj>());
@@ -1 +1 @@
1
- export { serializeBool } from "../naive/bool";
1
+ export { serializeBool, serializeBoolUnsafe } from "../naive/bool";
@@ -1,5 +1 @@
1
- export {
2
- serializeFloat,
3
- serializeFloat32,
4
- serializeFloat64,
5
- } from "../naive/float";
1
+ export { serializeFloat32, serializeFloat64 } from "../naive/float";
@@ -1 +1 @@
1
- export { serializeInteger } from "../naive/integer";
1
+ export { serializeInteger, serializeIntegerUnsafe } from "../naive/integer";
@@ -0,0 +1,51 @@
1
+ import { bs } from "../../../lib/as-bs";
2
+ import { JSON } from "../..";
3
+ import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../custom/chars";
4
+ import { serializeArbitrary } from "./arbitrary";
5
+
6
+ export function serializeJsonArray(src: JSON.Arr): void {
7
+ const n = src.length;
8
+
9
+ if (n == 0) {
10
+ bs.proposeSize(4);
11
+ store<u32>(bs.offset, 6094939); // "[]"
12
+ bs.offset += 4;
13
+ return;
14
+ }
15
+
16
+ const vals = src._vals;
17
+ // Source base + upper bound for resolving a deferred slot's value range.
18
+ const srcBase = changetype<usize>(src._src);
19
+ const srcEnd = srcBase + ((<usize>src._src.length) << 1);
20
+
21
+ bs.proposeSize(4 + <u32>(n - 1) * 2);
22
+ store<u16>(bs.offset, BRACKET_LEFT);
23
+ bs.offset += 2;
24
+
25
+ // A still-deferred slot is copied out verbatim from the source; an
26
+ // eager/materialized slot is serialized from its boxed bits.
27
+ for (let i = 0; i < n; i++) {
28
+ if (i != 0) {
29
+ store<u16>(bs.offset, COMMA);
30
+ bs.offset += 2;
31
+ }
32
+ const slot = unchecked(vals[i]);
33
+ if (JSON.Value.slotIsLazy(slot)) {
34
+ const start = JSON.Value.slotPtr(slot, srcBase);
35
+ const end = JSON.Value.slotEnd(slot, srcBase, srcEnd);
36
+ const size = end - start;
37
+ bs.proposeSize(size);
38
+ memory.copy(bs.offset, start, size);
39
+ bs.offset += size;
40
+ } else {
41
+ const v = JSON.Value.fromBits(slot);
42
+ serializeArbitrary(v);
43
+ // Persist a cached String escape-class back into the slot (see object.ts).
44
+ const nb = v.__bits();
45
+ if (nb != slot) unchecked((vals[i] = nb));
46
+ }
47
+ }
48
+
49
+ store<u16>(bs.offset, BRACKET_RIGHT);
50
+ bs.offset += 2;
51
+ }
@@ -5,6 +5,23 @@ import { serializeArbitrary } from "./arbitrary";
5
5
  import { serializeStringRange } from "../naive/string";
6
6
 
7
7
  export function serializeObject(src: JSON.Obj): void {
8
+ const rawStart = src._rawStart;
9
+ if (rawStart != 0) {
10
+ const rawSize = src._rawEnd - rawStart;
11
+ // At top level, `bs.out` can copy the anchored source slice straight into
12
+ // the returned string. Nested objects already have parent bytes in the
13
+ // staging buffer and therefore keep the regular append path below.
14
+ if (bs.offset == bs.buffer && bs.cacheOutput == 0) {
15
+ bs.cacheOutput = rawStart;
16
+ bs.cacheOutputLen = rawSize;
17
+ return;
18
+ }
19
+ bs.proposeSize(rawSize);
20
+ memory.copy(bs.offset, rawStart, rawSize);
21
+ bs.offset += rawSize;
22
+ return;
23
+ }
24
+
8
25
  const srcSize = src.size;
9
26
 
10
27
  if (srcSize == 0) {
@@ -17,13 +34,18 @@ export function serializeObject(src: JSON.Obj): void {
17
34
  const vals = src._vals;
18
35
  const kbuf = changetype<usize>(src._kbuf);
19
36
  const kused = src._kused;
37
+ // Source base + upper bound for resolving a deferred slot's value range.
38
+ const srcBase = changetype<usize>(src._src);
39
+ const srcEnd = srcBase + ((<usize>src._src.length) << 1);
20
40
 
21
41
  bs.proposeSize(4 + <u32>(srcSize - 1) * 2 + <u32>srcSize * 2);
22
42
  store<u16>(bs.offset, BRACE_LEFT);
23
43
  bs.offset += 2;
24
44
 
25
- // Walk the length-prefixed key buffer in lockstep with the values, emitting
26
- // each key straight from its slice (no per-key string materialization).
45
+ // Walk the length-prefixed key buffer in lockstep with the value slots,
46
+ // emitting each key straight from its slice (no per-key string
47
+ // materialization). A still-deferred slot is copied out verbatim from the
48
+ // source; an eager/materialized slot is serialized from its boxed bits.
27
49
  let pos = 0;
28
50
  let i = 0;
29
51
  while (pos < kused) {
@@ -35,7 +57,24 @@ export function serializeObject(src: JSON.Obj): void {
35
57
  serializeStringRange(kbuf + ((<usize>(pos + 1)) << 1), (<usize>len) << 1);
36
58
  store<u16>(bs.offset, COLON);
37
59
  bs.offset += 2;
38
- serializeArbitrary(unchecked(vals[i]));
60
+
61
+ const slot = unchecked(vals[i]);
62
+ if (JSON.Value.slotIsLazy(slot)) {
63
+ const start = JSON.Value.slotPtr(slot, srcBase);
64
+ const end = JSON.Value.slotEnd(slot, srcBase, srcEnd);
65
+ const size = end - start;
66
+ bs.proposeSize(size);
67
+ memory.copy(bs.offset, start, size);
68
+ bs.offset += size;
69
+ } else {
70
+ const v = JSON.Value.fromBits(slot);
71
+ serializeArbitrary(v);
72
+ // Persist any escape-class the serializer cached on a materialized String
73
+ // slot back into the flat slot, so re-serializing this object reuses it
74
+ // (the memcpy fast path) instead of re-scanning. No-op for other types.
75
+ const nb = v.__bits();
76
+ if (nb != slot) unchecked((vals[i] = nb));
77
+ }
39
78
  pos += 1 + len;
40
79
  i++;
41
80
  }
@@ -3,8 +3,7 @@ import { serializeString_NAIVE } from "../naive/string";
3
3
  import { serializeString_SIMD } from "../simd/string";
4
4
  import { serializeString_SWAR } from "../swar/string";
5
5
 
6
-
7
- @inline export function serializeString(src: string): void {
6
+ export function serializeString(src: string): void {
8
7
  if (JSON_MODE == JSONMode.SIMD) {
9
8
  serializeString_SIMD(src);
10
9
  } else if (JSON_MODE == JSONMode.NAIVE) {
@@ -9,8 +9,7 @@ import {
9
9
  serializeTypedArray,
10
10
  } from "../naive/typedarray";
11
11
 
12
-
13
- @inline export function serializeDynamic(type: u16, data: usize): void {
12
+ export function serializeDynamic(type: u16, data: usize): void {
14
13
  if (type == JSON.Types.ArrayBuffer) {
15
14
  serializeArrayBufferUnsafe(
16
15
  data,
@@ -4,6 +4,7 @@ export * from "./index/bool";
4
4
  export * from "./index/date";
5
5
  export * from "./index/float";
6
6
  export * from "./index/integer";
7
+ export * from "./index/jsonarray";
7
8
  export * from "./index/map";
8
9
  export * from "./index/object";
9
10
  export * from "./index/raw";
@@ -5,13 +5,9 @@ import { serializeBoolUnsafe } from "./bool";
5
5
  import { serializeFloat32Unsafe, serializeFloat64Unsafe } from "./float";
6
6
  import { serializeIntegerUnsafe } from "./integer";
7
7
  import { serializeString } from "../index/string";
8
- import {
9
- dragonbox_f32_buffered,
10
- dragonbox_f64_buffered,
11
- } from "../../util/dragonbox";
8
+ import { ensureItoaPairs, itoaFast } from "../../util/itoa-fast";
9
+ import { dtoa_buffered, ftoa_buffered } from "xjb-as";
12
10
 
13
-
14
- @inline
15
11
  function maxIntegerBytes<T extends number>(): u32 {
16
12
  if (sizeof<T>() == 1) return isSigned<T>() ? 8 : 6;
17
13
  if (sizeof<T>() == 2) return isSigned<T>() ? 12 : 10;
@@ -19,8 +15,6 @@ function maxIntegerBytes<T extends number>(): u32 {
19
15
  return isSigned<T>() ? 42 : 40;
20
16
  }
21
17
 
22
-
23
- @inline
24
18
  function reservePrimitiveArray<T>(len: i32): void {
25
19
  if (len <= 0) return;
26
20
  if (isBoolean<T>()) {
@@ -34,8 +28,6 @@ function reservePrimitiveArray<T>(len: i32): void {
34
28
  }
35
29
  }
36
30
 
37
-
38
- @inline
39
31
  function serializeArrayElement<T>(value: T): void {
40
32
  if (isString<T>()) {
41
33
  serializeString(value as string);
@@ -78,15 +70,13 @@ function serializeArrayElement<T>(value: T): void {
78
70
  // comma-store + advance per element that the generic `serializeArray` does.
79
71
 
80
72
  // `"true,"` packed UTF-16: `t,r,u,e,,` lanes 0..4 = bytes 0..9.
81
- @inline const TRUE_COMMA_LO: u64 = 0x0065_0075_0072_0074;
73
+ const TRUE_COMMA_LO: u64 = 0x0065_0075_0072_0074;
82
74
 
83
-
84
- @inline const TRUE_COMMA_HI: u16 = 0x002c;
75
+ const TRUE_COMMA_HI: u16 = 0x002c;
85
76
  // `"false,"` packed UTF-16: `f,a,l,s,e,,` lanes 0..5 = bytes 0..11.
86
- @inline const FALSE_COMMA_LO: u64 = 0x0073_006c_0061_0066;
87
-
77
+ const FALSE_COMMA_LO: u64 = 0x0073_006c_0061_0066;
88
78
 
89
- @inline const FALSE_COMMA_HI: u32 = 0x002c_0065;
79
+ const FALSE_COMMA_HI: u32 = 0x002c_0065;
90
80
 
91
81
  function serializeBoolArrayFast(src: bool[]): void {
92
82
  const len = src.length;
@@ -152,8 +142,7 @@ function initU8Lut(): void {
152
142
  _u8LutInited = true;
153
143
  }
154
144
 
155
-
156
- @inline function ensureU8Lut(): void {
145
+ function ensureU8Lut(): void {
157
146
  if (!_u8LutInited) initU8Lut();
158
147
  }
159
148
 
@@ -181,20 +170,20 @@ function serializeU8ArrayFast(src: u8[]): void {
181
170
  store<u16>(bs.offset - 2, BRACKET_RIGHT);
182
171
  }
183
172
 
184
- // Specialized float-array serializer: dragonbox + trailing comma in a
173
+ // Specialized float-array serializer: xjb writer + trailing comma in a
185
174
  // uniform per-iteration body, then overwrite the final comma with `]`. The
186
175
  // generic dispatcher splits the loop into "N-1 elements with comma, then
187
- // last element without, then `]`" the branch on each `i < end` check
176
+ // last element without, then `]`" - the branch on each `i < end` check
188
177
  // stalls the loop's tight bs.offset advance pattern. This variant runs the
189
- // same number of stores per iteration (dragonbox output + COMMA), but the
178
+ // same number of stores per iteration (xjb output + COMMA), but the
190
179
  // uniform loop body inlines better and the trailing `]` is a single fixed
191
180
  // overwrite outside the loop.
192
181
  function serializeF64ArrayFast(src: f64[]): void {
193
182
  const len = src.length;
194
- // Worst case per element: ~24 chars for f64 + comma = 50 bytes.
195
- // Slight over-reserve (66) matches the existing `reservePrimitiveArray`
196
- // budget and keeps a safety margin for any NaN/Inf spelling.
197
- bs.proposeSize(4 + <u32>len * 66);
183
+ // Worst case per element: a 21-char fixed integer (e.g. 1e20) or a ~24-char
184
+ // exponent form, + comma = ~52 bytes; plus the writer's SIMD stores can
185
+ // overshoot the logical end by up to 16 bytes. 80 covers both comfortably.
186
+ bs.proposeSize(4 + <u32>len * 80);
198
187
  store<u16>(bs.offset, BRACKET_LEFT);
199
188
  bs.offset += 2;
200
189
  if (len == 0) {
@@ -210,9 +199,9 @@ function serializeF64ArrayFast(src: f64[]): void {
210
199
  let offset = bs.offset;
211
200
  for (let i: i32 = 0; i < len; i++) {
212
201
  const v = load<f64>(dataStart + ((<usize>i) << 3));
213
- const size = dragonbox_f64_buffered(offset, v) << 1;
214
- store<u16>(offset + size, COMMA);
215
- offset += size + 2;
202
+ offset += dtoa_buffered(offset, v) << 1;
203
+ store<u16>(offset, COMMA);
204
+ offset += 2;
216
205
  }
217
206
  // Overwrite the final trailing comma with `]`.
218
207
  store<u16>(offset - 2, BRACKET_RIGHT);
@@ -221,9 +210,10 @@ function serializeF64ArrayFast(src: f64[]): void {
221
210
 
222
211
  function serializeF32ArrayFast(src: f32[]): void {
223
212
  const len = src.length;
224
- // Worst case for f32 is ~16 chars + comma = ~34 bytes; mirror the budget
225
- // used by `reservePrimitiveArray`.
226
- bs.proposeSize(4 + <u32>len * 34);
213
+ // Worst case for f32 is a 21-char fixed integer (e.g. 1e20 rounds to a
214
+ // 21-digit value), + comma = ~44 bytes, plus up to 16 bytes of SIMD store
215
+ // overshoot. 64 covers it.
216
+ bs.proposeSize(4 + <u32>len * 64);
227
217
  store<u16>(bs.offset, BRACKET_LEFT);
228
218
  bs.offset += 2;
229
219
  if (len == 0) {
@@ -236,9 +226,9 @@ function serializeF32ArrayFast(src: f32[]): void {
236
226
  let offset = bs.offset;
237
227
  for (let i: i32 = 0; i < len; i++) {
238
228
  const v = load<f32>(dataStart + ((<usize>i) << 2));
239
- const size = dragonbox_f32_buffered(offset, v) << 1;
240
- store<u16>(offset + size, COMMA);
241
- offset += size + 2;
229
+ offset += ftoa_buffered(offset, v) << 1;
230
+ store<u16>(offset, COMMA);
231
+ offset += 2;
242
232
  }
243
233
  store<u16>(offset - 2, BRACKET_RIGHT);
244
234
  bs.offset = offset;
@@ -276,6 +266,59 @@ function serializeI8ArrayFast(src: i8[]): void {
276
266
  store<u16>(bs.offset - 2, BRACKET_RIGHT);
277
267
  }
278
268
 
269
+ // Integer arrays wider than one byte use the same uniform trailing-comma
270
+ // layout as the specialized float paths. Keeping the output cursor local
271
+ // avoids two global `bs.offset` accesses per value, while reading dataStart
272
+ // directly removes the Array.__uget call left in the generic array loop.
273
+ function serializeIntegerArrayFast<T extends number[]>(src: T): void {
274
+ const len = src.length;
275
+ bs.proposeSize(4 + <u32>len * (maxIntegerBytes<valueof<T>>() + 2));
276
+ store<u16>(bs.offset, BRACKET_LEFT);
277
+ bs.offset += 2;
278
+ if (len == 0) {
279
+ store<u16>(bs.offset, BRACKET_RIGHT);
280
+ bs.offset += 2;
281
+ return;
282
+ }
283
+
284
+ ensureItoaPairs();
285
+ const dataStart = src.dataStart;
286
+ const stride = sizeof<valueof<T>>();
287
+ let offset = bs.offset;
288
+ for (let i: i32 = 0; i < len; i++) {
289
+ const value = load<valueof<T>>(dataStart + <usize>i * stride);
290
+ offset += (<usize>itoaFast<valueof<T>>(offset, value)) << 1;
291
+ store<u16>(offset, COMMA);
292
+ offset += 2;
293
+ }
294
+ store<u16>(offset - 2, BRACKET_RIGHT);
295
+ bs.offset = offset;
296
+ }
297
+
298
+ function serializeStringArrayFast(src: string[]): void {
299
+ const len = src.length;
300
+ // String serialization reserves its own payload; reserve delimiters here.
301
+ bs.proposeSize(4 + (len > 0 ? <u32>(len - 1) * 2 : 0));
302
+ store<u16>(bs.offset, BRACKET_LEFT);
303
+ bs.offset += 2;
304
+ if (len == 0) {
305
+ store<u16>(bs.offset, BRACKET_RIGHT);
306
+ bs.offset += 2;
307
+ return;
308
+ }
309
+
310
+ const dataStart = src.dataStart;
311
+ for (let i: i32 = 0; i < len; i++) {
312
+ const value = changetype<string>(
313
+ load<usize>(dataStart + ((<usize>i) << 2)),
314
+ );
315
+ serializeString(value);
316
+ store<u16>(bs.offset, COMMA);
317
+ bs.offset += 2;
318
+ }
319
+ store<u16>(bs.offset - 2, BRACKET_RIGHT);
320
+ }
321
+
279
322
  export function serializeArray<T extends any[]>(src: T): void {
280
323
  // Specialized fast paths fold the per-element comma into the element write,
281
324
  // saving one `store<u16>` + advance per iteration. AS folds the type checks
@@ -303,6 +346,16 @@ export function serializeArray<T extends any[]>(src: T): void {
303
346
  serializeI8ArrayFast(changetype<i8[]>(src));
304
347
  return;
305
348
  }
349
+ if (isInteger<valueof<T>>()) {
350
+ // u8[] and i8[] returned through their lookup-table paths above.
351
+ serializeIntegerArrayFast<T>(src);
352
+ return;
353
+ }
354
+ if (isString<valueof<T>>()) {
355
+ // @ts-expect-error: T is string[]
356
+ serializeStringArrayFast(changetype<string[]>(src));
357
+ return;
358
+ }
306
359
  if (isFloat<valueof<T>>() && sizeof<valueof<T>>() == 8) {
307
360
  // @ts-expect-error: T is f64[]
308
361
  serializeF64ArrayFast(changetype<f64[]>(src));
@@ -4,7 +4,6 @@ import { bs } from "../../../lib/as-bs";
4
4
  * @param data data to serialize
5
5
  * @returns void
6
6
  */
7
- @inline
8
7
  export function serializeBoolUnsafe(data: bool): void {
9
8
  if (data === true) {
10
9
  store<u64>(bs.offset, 28429475166421108);
@@ -1,43 +1,29 @@
1
1
  import { bs } from "../../../lib/as-bs";
2
- import {
3
- dragonbox_f32_buffered,
4
- dragonbox_f64_buffered,
5
- } from "../../util/dragonbox";
2
+ import { dtoa_buffered, ftoa_buffered } from "xjb-as";
6
3
 
4
+ // Float serialization is backed by xjb-as (a Schubfach/xjb shortest-decimal
5
+ // writer with SWAR + WASM-SIMD digit kernels). The *_buffered writers emit
6
+ // UTF-16 straight into the `bs` buffer and return the number of characters
7
+ // written, so advancing the offset is `count << 1` bytes.
7
8
 
8
- @inline
9
9
  export function serializeFloat32Unsafe(data: f32): void {
10
- const size = dragonbox_f32_buffered(bs.offset, data) << 1;
11
- bs.offset += size;
10
+ bs.offset += ftoa_buffered(bs.offset, data) << 1;
12
11
  }
13
12
 
14
-
15
- @inline
16
13
  export function serializeFloat64Unsafe(data: f64): void {
17
- const size = dragonbox_f64_buffered(bs.offset, data) << 1;
18
- bs.offset += size;
14
+ bs.offset += dtoa_buffered(bs.offset, data) << 1;
19
15
  }
20
16
 
21
-
22
- @inline
23
17
  export function serializeFloat32(data: f32): void {
24
- bs.ensureSize(64);
25
- const size = dragonbox_f32_buffered(bs.offset, data) << 1;
26
- bs.stackSize += size;
27
- bs.offset += size;
18
+ bs.ensureSize(128);
19
+ const bytes = ftoa_buffered(bs.offset, data) << 1;
20
+ bs.stackSize += bytes;
21
+ bs.offset += bytes;
28
22
  }
29
23
 
30
-
31
- @inline
32
24
  export function serializeFloat64(data: f64): void {
33
- bs.ensureSize(64);
34
- const size = dragonbox_f64_buffered(bs.offset, data) << 1;
35
- bs.stackSize += size;
36
- bs.offset += size;
37
- }
38
-
39
- // @ts-ignore: inline
40
- @inline export function serializeFloat<T extends number>(data: T): void {
41
- if (sizeof<T>() == 4) serializeFloat32(<f32>data);
42
- else serializeFloat64(<f64>data);
25
+ bs.ensureSize(128);
26
+ const bytes = dtoa_buffered(bs.offset, data) << 1;
27
+ bs.stackSize += bytes;
28
+ bs.offset += bytes;
43
29
  }
@@ -1,16 +1,12 @@
1
1
  import { bs } from "../../../lib/as-bs";
2
2
  import { ensureItoaPairs, itoaFast } from "../../util/itoa-fast";
3
3
 
4
-
5
- @inline
6
4
  export function serializeIntegerUnsafe<T extends number>(data: T): void {
7
5
  ensureItoaPairs();
8
6
  const charsWritten = itoaFast<T>(bs.offset, data);
9
7
  bs.offset += (<usize>charsWritten) << 1;
10
8
  }
11
-
12
- // @ts-ignore: inline
13
- @inline export function serializeInteger<T extends number>(data: T): void {
9
+ export function serializeInteger<T extends number>(data: T): void {
14
10
  ensureItoaPairs();
15
11
  bs.ensureSize(sizeof<T>() << 3);
16
12
  const charsWritten = itoaFast<T>(bs.offset, data);
@@ -1,6 +1,34 @@
1
1
  import { bs } from "../../../lib/as-bs";
2
2
  import { JSON } from "../..";
3
3
  import { BRACE_LEFT, BRACE_RIGHT, COLON, COMMA } from "../../custom/chars";
4
+ import { serializeString } from "../index/string";
5
+ import { serializeRaw } from "./raw";
6
+
7
+ function serializeRawMapFast<T extends Map<any, any>>(
8
+ keys: Array<indexof<T>>,
9
+ values: Array<valueof<T>>,
10
+ ): void {
11
+ const len = keys.length;
12
+ bs.proposeSize(4 + <u32>len * 4);
13
+ store<u16>(bs.offset, BRACE_LEFT);
14
+ bs.offset += 2;
15
+
16
+ const keyData = keys.dataStart;
17
+ const valueData = values.dataStart;
18
+ for (let i = 0; i < len; i++) {
19
+ const key = changetype<string>(load<usize>(keyData + ((<usize>i) << 2)));
20
+ const value = changetype<JSON.Raw>(
21
+ load<usize>(valueData + ((<usize>i) << 2)),
22
+ );
23
+ serializeString(key);
24
+ store<u16>(bs.offset, COLON);
25
+ bs.offset += 2;
26
+ serializeRaw(value);
27
+ store<u16>(bs.offset, COMMA);
28
+ bs.offset += 2;
29
+ }
30
+ store<u16>(bs.offset - 2, BRACE_RIGHT);
31
+ }
4
32
 
5
33
  export function serializeMap<T extends Map<any, any>>(src: T): void {
6
34
  const srcSize = src.size;
@@ -17,6 +45,15 @@ export function serializeMap<T extends Map<any, any>>(src: T): void {
17
45
  let values = src.values();
18
46
  const keyIsString = isString<indexof<T>>();
19
47
 
48
+ if (keyIsString && isReference<valueof<T>>()) {
49
+ const valueType = changetype<nonnull<valueof<T>>>(0);
50
+ // @ts-ignore: instanceof on the reference value type
51
+ if (valueType instanceof JSON.Raw) {
52
+ serializeRawMapFast<T>(keys, values);
53
+ return;
54
+ }
55
+ }
56
+
20
57
  bs.proposeSize(4 + <u32>(srcSize - 1) * 2 + <u32>srcSize * 2);
21
58
 
22
59
  store<u16>(bs.offset, BRACE_LEFT);
@@ -7,8 +7,7 @@ import { bytes } from "../../util";
7
7
  * @param data data to serialize
8
8
  * @returns void
9
9
  */
10
- // @ts-ignore: inline
11
- @inline export function serializeRaw(data: JSON.Raw): void {
10
+ export function serializeRaw(data: JSON.Raw): void {
12
11
  const dataSize = bytes(data.data);
13
12
  bs.proposeSize(dataSize);
14
13
  memory.copy(
@@ -6,8 +6,6 @@ import { serializeFloat32Unsafe, serializeFloat64Unsafe } from "./float";
6
6
  import { serializeIntegerUnsafe } from "./integer";
7
7
  import { serializeString } from "../index/string";
8
8
 
9
-
10
- @inline
11
9
  function maxIntegerBytes<T extends number>(): u32 {
12
10
  if (sizeof<T>() == 1) return isSigned<T>() ? 8 : 6;
13
11
  if (sizeof<T>() == 2) return isSigned<T>() ? 12 : 10;
@@ -15,10 +13,7 @@ function maxIntegerBytes<T extends number>(): u32 {
15
13
  return isSigned<T>() ? 42 : 40;
16
14
  }
17
15
 
18
-
19
- @inline
20
16
  function reservePrimitiveSet<T>(len: i32): void {
21
- if (len <= 0) return;
22
17
  if (isBoolean<T>()) {
23
18
  bs.proposeSize(4 + <u32>len * 12);
24
19
  } else if (isInteger<T>()) {
@@ -6,19 +6,13 @@ import { serializeFloat32Unsafe, serializeFloat64Unsafe } from "./float";
6
6
  import { serializeIntegerUnsafe } from "./integer";
7
7
  import { serializeString } from "../index/string";
8
8
 
9
-
10
- @inline
11
9
  function maxIntegerBytes<T extends number>(): u32 {
12
10
  if (sizeof<T>() == 1) return isSigned<T>() ? 8 : 6;
13
11
  if (sizeof<T>() == 2) return isSigned<T>() ? 12 : 10;
14
12
  if (sizeof<T>() == 4) return isSigned<T>() ? 22 : 20;
15
13
  return isSigned<T>() ? 42 : 40;
16
14
  }
17
-
18
-
19
- @inline
20
15
  function reservePrimitiveStaticArray<T>(len: i32): void {
21
- if (len <= 0) return;
22
16
  if (isBoolean<T>()) {
23
17
  bs.proposeSize(4 + <u32>len * 12);
24
18
  } else if (isInteger<T>()) {