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.
- package/CHANGELOG.md +5 -1
- package/README.md +42 -0
- package/assembly/deserialize/error.ts +21 -0
- package/assembly/deserialize/index/arbitrary.ts +29 -8
- package/assembly/deserialize/index/array.ts +2 -1
- package/assembly/deserialize/index/bool.ts +1 -1
- package/assembly/deserialize/index/object.ts +3 -0
- package/assembly/deserialize/index/string.ts +37 -0
- package/assembly/deserialize/index.ts +0 -1
- package/assembly/deserialize/naive/array/arbitrary.ts +3 -2
- package/assembly/deserialize/naive/array/array.ts +1 -0
- package/assembly/deserialize/naive/array/bool.ts +21 -11
- package/assembly/deserialize/naive/array/box.ts +13 -33
- package/assembly/deserialize/naive/array/float.ts +23 -13
- package/assembly/deserialize/naive/array/integer.ts +33 -21
- package/assembly/deserialize/naive/array/object.ts +1 -0
- package/assembly/deserialize/naive/array/string.ts +29 -16
- package/assembly/deserialize/naive/bool.ts +17 -5
- package/assembly/deserialize/naive/float.ts +1 -2
- package/assembly/deserialize/naive/map.ts +339 -39
- package/assembly/deserialize/naive/object.ts +122 -65
- package/assembly/deserialize/naive/raw.ts +13 -1
- package/assembly/deserialize/naive/set.ts +0 -1
- package/assembly/deserialize/naive/staticarray.ts +3 -2
- package/assembly/deserialize/naive/string.ts +41 -52
- package/assembly/deserialize/parseMode.ts +216 -0
- package/assembly/deserialize/simd/array/integer.ts +6 -3
- package/assembly/deserialize/simd/float.ts +35 -29
- package/assembly/deserialize/simd/string.ts +79 -17
- package/assembly/deserialize/string-validation.ts +34 -0
- package/assembly/deserialize/swar/array/array.ts +63 -13
- package/assembly/deserialize/swar/array/bool.ts +14 -3
- package/assembly/deserialize/swar/array/float.ts +394 -13
- package/assembly/deserialize/swar/array/generic.ts +12 -2
- package/assembly/deserialize/swar/array/integer.ts +22 -62
- package/assembly/deserialize/swar/array/object.ts +11 -2
- package/assembly/deserialize/swar/array/shared.ts +1 -1
- package/assembly/deserialize/swar/array/string.ts +34 -5
- package/assembly/deserialize/swar/array/struct.ts +109 -15
- package/assembly/deserialize/swar/float.ts +19 -12
- package/assembly/deserialize/swar/string.ts +70 -111
- package/assembly/index.d.ts +6 -0
- package/assembly/index.ts +411 -33
- package/assembly/serialize/index/bool.ts +1 -1
- package/assembly/serialize/index/float.ts +1 -5
- package/assembly/serialize/index/integer.ts +1 -1
- package/assembly/serialize/index/object.ts +17 -0
- package/assembly/serialize/naive/array.ts +64 -0
- package/assembly/serialize/naive/float.ts +0 -5
- package/assembly/serialize/naive/map.ts +37 -0
- package/assembly/serialize/naive/set.ts +0 -1
- package/assembly/serialize/naive/staticarray.ts +0 -1
- package/assembly/serialize/naive/string.ts +0 -6
- package/assembly/serialize/naive/typedarray.ts +0 -1
- package/assembly/util/eisel-lemire.ts +179 -0
- package/assembly/util/prettyWhitespaceSimd.ts +69 -0
- package/assembly/util/scanValueEndSimd.ts +59 -36
- package/assembly/util/validateJson.ts +338 -0
- package/lib/as-bs.ts +28 -6
- package/package.json +13 -3
- package/transform/lib/index.d.ts +6 -0
- package/transform/lib/index.js +770 -111
- package/transform/lib/types.d.ts +1 -0
- package/transform/lib/types.js +3 -0
- package/assembly/deserialize/index/struct.ts +0 -1
- package/assembly/deserialize/naive/struct.ts +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 2026-08-03 - v1.6.0
|
|
4
|
+
|
|
5
|
+
- perf: much faster deserialization by using default values as a cache.
|
|
6
|
+
- fix(transform): resolve imported nested custom types across source-file dependencies and invoke their custom deserializers, including for nullable and reordered fields ([#215](https://github.com/JairusSW/json-as/pull/215)).
|
|
7
|
+
- chore: add Guillermo Diez to the contributor list.
|
|
4
8
|
|
|
5
9
|
## 2026-06-10 - v1.5.0
|
|
6
10
|
|
package/README.md
CHANGED
|
@@ -604,6 +604,48 @@ How `json-as` stacks up against other JSON libraries on a ~5 KiB GitHub-repo pay
|
|
|
604
604
|
|
|
605
605
|
<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/library-serialize.svg" alt="Library comparison - serialize throughput">
|
|
606
606
|
|
|
607
|
+
### Runtime Comparison
|
|
608
|
+
|
|
609
|
+
How fast the **same** `json-as` classic bench deserializes the minified payloads across six WebAssembly runtimes — including [WARP](https://github.com/wasm-ecosystem/wasm-compiler), a single-pass compiler built for embedded targets, alongside the optimizing JITs (Wasmtime, WAVM), the pure-Go wazero, and JS engines (V8, Bun).
|
|
610
|
+
|
|
611
|
+
Each runtime runs the real `bench()` lib (warm up, time the loop, report MB/s itself) on a `NAIVE`-mode build with a shared feature set (no SIMD / bulk-memory / non-trapping float-to-int) so the executed code is equivalent. The WASI runtimes read the payload over WASI; V8/Bun via an `env`-ABI host. WARP has no WASI and — by design ("no recursions") — can't re-enter the module from a host import, so it runs through a small custom C++ host that links `performance.now`/`console.log`/`writeFile` with the payload embedded. The timed run is split into small frames with a full GC between them (the bench lib's `BENCH_FRAMES`, applied to every runtime so the measurement is identical); this excludes stop-the-world GC pauses and keeps WARP — an embedded, single-shot-oriented compiler — inside its stable envelope. Even so, WARP's single-pass codegen lands within a few percent of the optimizing JITs.
|
|
612
|
+
|
|
613
|
+
<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/runtimes-deserialize.svg" alt="Deserialization throughput across WebAssembly runtimes">
|
|
614
|
+
|
|
615
|
+
<details>
|
|
616
|
+
<summary>Serialization throughput (click to expand)</summary>
|
|
617
|
+
|
|
618
|
+
<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/runtimes-serialize.svg" alt="Serialization throughput across WebAssembly runtimes">
|
|
619
|
+
</details>
|
|
620
|
+
|
|
621
|
+
Reproduce locally (the JS engines and standalone runtimes are auto-detected; point `WARP_SRC` at a [wasm-ecosystem/wasm-compiler](https://github.com/wasm-ecosystem/wasm-compiler) checkout with its libs built — see the script header for the cmake flags):
|
|
622
|
+
|
|
623
|
+
```bash
|
|
624
|
+
WARP_SRC=/path/to/wasm-compiler npm run bench:runtimes
|
|
625
|
+
npm run charts:runtimes
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Compiler Comparison
|
|
629
|
+
|
|
630
|
+
A different axis: same source, same runtime (V8), different **compiler**. [warpo](https://github.com/wasm-ecosystem/warpo) is a next-generation AssemblyScript compiler; this compares `json-as` when built with stock `asc` vs `warpo`, each before and after a `wasm-opt -O4` pass.
|
|
631
|
+
|
|
632
|
+
warpo produces consistently faster code on both directions, and the gap widens with number/structure density (Canada). `wasm-opt -O4` barely moves either (both already run binaryen optimizations), so the difference is the compilers' own codegen, not a missing post-pass.
|
|
633
|
+
|
|
634
|
+
<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/warpo-vs-asc-deserialize.svg" alt="json-as deserialization: asc vs warpo compiler on V8">
|
|
635
|
+
|
|
636
|
+
<details>
|
|
637
|
+
<summary>Serialization (click to expand)</summary>
|
|
638
|
+
|
|
639
|
+
<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/warpo-vs-asc-serialize.svg" alt="json-as serialization: asc vs warpo compiler on V8">
|
|
640
|
+
</details>
|
|
641
|
+
|
|
642
|
+
warpo can't run asc transform plugins, so the bench compiles the post-transform `.tmp.ts` (emitted via `JSON_WRITE`); see `scripts/run-bench.warpo.sh`. Reproduce:
|
|
643
|
+
|
|
644
|
+
```bash
|
|
645
|
+
npm run bench:warpo # builds with asc + warpo, wasm-opt -O4 each, runs on v8
|
|
646
|
+
npm run charts:warpo
|
|
647
|
+
```
|
|
648
|
+
|
|
607
649
|
### Lazy Fields
|
|
608
650
|
|
|
609
651
|
Mark a field `@lazy` (or `JSON.Lazy<T>`, or a whole class with `@json({ lazy: "auto" })`) to defer it: its raw JSON slice is stored at parse time and parsed only on first access. Fields you skip are never parsed, and untouched fields pass through their original bytes on serialize. See the [Lazy Fields guide](https://docs.jairus.dev/json-as/guide/lazy-fields) for the full API and trade-offs.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Malformed-range failures are recorded only on the cold error path and
|
|
2
|
+
// consumed at a public JSON.parse boundary. Keeping the final throw there
|
|
3
|
+
// makes it catchable by try-as without adding a success-path store or a
|
|
4
|
+
// second validation scan.
|
|
5
|
+
let productionParseError = false;
|
|
6
|
+
|
|
7
|
+
export function markProductionParseError(): void {
|
|
8
|
+
productionParseError = true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function takeProductionParseError(): bool {
|
|
12
|
+
if (!productionParseError) return false;
|
|
13
|
+
productionParseError = false;
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Mark a malformed cold path and return the shared zero cursor sentinel. */
|
|
18
|
+
export function failProductionParse(): usize {
|
|
19
|
+
productionParseError = true;
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import { JSON } from "../..";
|
|
2
|
-
import {
|
|
2
|
+
import { deserializeBooleanCode } from "./bool";
|
|
3
3
|
import { deserializeFloat } from "./float";
|
|
4
4
|
import { deserializeObject, deserializeJsonArray, getParseSrc } from "./object";
|
|
5
5
|
import { deserializeString } from "./string";
|
|
6
6
|
import { BRACE_LEFT, BRACKET_LEFT, CHAR_N, QUOTE } from "../../custom/chars";
|
|
7
7
|
|
|
8
|
+
const NULL_WORD: u64 = 30399761348886638;
|
|
9
|
+
|
|
8
10
|
export function deserializeArbitrary(
|
|
9
11
|
srcStart: usize,
|
|
10
12
|
srcEnd: usize,
|
|
11
13
|
dst: usize,
|
|
12
14
|
): JSON.Value {
|
|
13
15
|
const v = parseArbitraryValue(srcStart, srcEnd);
|
|
16
|
+
if (changetype<usize>(v) == 0) return v;
|
|
14
17
|
// Reuse path (`JSON.parse<JSON.Value>(data, out)`): write the parsed bits into
|
|
15
18
|
// the caller's handle (with the GC barrier for any managed payload).
|
|
16
19
|
return dst != 0 ? JSON.Value.__adoptInto(dst, v) : v;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
function parseArbitraryValue(srcStart: usize, srcEnd: usize): JSON.Value {
|
|
23
|
+
if (srcStart >= srcEnd) return changetype<JSON.Value>(0);
|
|
20
24
|
const firstChar = load<u16>(srcStart);
|
|
21
25
|
if (
|
|
22
26
|
firstChar == QUOTE ||
|
|
@@ -29,19 +33,36 @@ function parseArbitraryValue(srcStart: usize, srcEnd: usize): JSON.Value {
|
|
|
29
33
|
const src = getParseSrc();
|
|
30
34
|
if (src.length != 0) {
|
|
31
35
|
const end = JSON.Util.scanValueEnd<JSON.Value>(srcStart, srcEnd);
|
|
36
|
+
// A zero end is the scanner's failure sentinel. Never retain it in a
|
|
37
|
+
// lazy slice: subtracting the source base from zero would underflow and
|
|
38
|
+
// later materialization could read outside the source value.
|
|
39
|
+
if (end == 0) return changetype<JSON.Value>(0);
|
|
32
40
|
return JSON.Value.fromSlice(srcStart, end, src);
|
|
33
41
|
}
|
|
34
|
-
if (firstChar == QUOTE)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
if (firstChar == QUOTE) {
|
|
43
|
+
const value = deserializeString(srcStart, srcEnd);
|
|
44
|
+
return changetype<usize>(value) != 0
|
|
45
|
+
? JSON.Value.from(value)
|
|
46
|
+
: changetype<JSON.Value>(0);
|
|
47
|
+
}
|
|
48
|
+
const composite =
|
|
49
|
+
firstChar == BRACE_LEFT
|
|
50
|
+
? changetype<usize>(deserializeObject(srcStart, srcEnd, 0))
|
|
51
|
+
: changetype<usize>(deserializeJsonArray(srcStart, srcEnd, 0));
|
|
52
|
+
return composite != 0
|
|
53
|
+
? firstChar == BRACE_LEFT
|
|
54
|
+
? JSON.Value.from(changetype<JSON.Obj>(composite))
|
|
55
|
+
: JSON.Value.from(changetype<JSON.Arr>(composite))
|
|
56
|
+
: changetype<JSON.Value>(0);
|
|
39
57
|
} else if (firstChar - 48 <= 9 || firstChar == 45) {
|
|
40
58
|
return JSON.Value.from(deserializeFloat<f64>(srcStart, srcEnd));
|
|
41
59
|
} else if (firstChar == 116 || firstChar == 102) {
|
|
42
|
-
|
|
60
|
+
const code = deserializeBooleanCode(srcStart, srcEnd);
|
|
61
|
+
return code != 0 ? JSON.Value.from(code == 2) : changetype<JSON.Value>(0);
|
|
43
62
|
} else if (firstChar == CHAR_N) {
|
|
63
|
+
if (srcEnd - srcStart < 8 || load<u64>(srcStart) != NULL_WORD)
|
|
64
|
+
return changetype<JSON.Value>(0);
|
|
44
65
|
return JSON.Value.from<usize>(0);
|
|
45
66
|
}
|
|
46
|
-
return
|
|
67
|
+
return changetype<JSON.Value>(0);
|
|
47
68
|
}
|
|
@@ -11,6 +11,7 @@ import { deserializeObjectArray } from "../naive/array/object";
|
|
|
11
11
|
import { deserializeRawArray } from "../naive/array/raw";
|
|
12
12
|
import { deserializeStringArray_NAIVE } from "../naive/array/string";
|
|
13
13
|
import { deserializeStructArray } from "../naive/array/struct";
|
|
14
|
+
import { deserializeStructArray_SWAR } from "../swar/array/struct";
|
|
14
15
|
import { deserializeIntegerArray_SIMD } from "../simd/array/integer";
|
|
15
16
|
import { deserializeIntegerArray_SWAR } from "../swar/array/integer";
|
|
16
17
|
import { deserializeFloatArray_SWAR } from "../swar/array/float";
|
|
@@ -82,7 +83,7 @@ export function deserializeArray<T extends unknown[]>(
|
|
|
82
83
|
isDefined(type.__DESERIALIZE_SLOW) ||
|
|
83
84
|
isDefined(type.__DESERIALIZE_FAST)
|
|
84
85
|
) {
|
|
85
|
-
return
|
|
86
|
+
return deserializeStructArray_SWAR<T>(srcStart, srcEnd, dst);
|
|
86
87
|
}
|
|
87
88
|
throw new Error("Could not parse array of type " + nameof<T>() + "!");
|
|
88
89
|
} else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { deserializeBoolean } from "../naive/bool";
|
|
1
|
+
export { deserializeBoolean, deserializeBooleanCode } from "../naive/bool";
|
|
@@ -2,17 +2,24 @@ import { JSONMode } from "../..";
|
|
|
2
2
|
import {
|
|
3
3
|
deserializeString_NAIVE,
|
|
4
4
|
deserializeStringField_NAIVE,
|
|
5
|
+
deserializeStringFieldTrusted_NAIVE,
|
|
5
6
|
} from "../naive/string";
|
|
6
7
|
import {
|
|
7
8
|
deserializeString_SIMD,
|
|
8
9
|
deserializeStringField_SIMD,
|
|
10
|
+
deserializeStringFieldTrusted_SIMD,
|
|
9
11
|
} from "../simd/string";
|
|
10
12
|
import {
|
|
11
13
|
deserializeString_SWAR,
|
|
12
14
|
deserializeStringField_SWAR,
|
|
15
|
+
deserializeStringFieldTrusted_SWAR,
|
|
13
16
|
} from "../swar/string";
|
|
14
17
|
|
|
15
18
|
export function deserializeString(srcStart: usize, srcEnd: usize): string {
|
|
19
|
+
// Whole-value decoders strip two UTF-16 code units before entering their
|
|
20
|
+
// optimized loops. Guard the actual memory-safety invariant here; complete
|
|
21
|
+
// RFC quote framing remains the strict-mode validator's job.
|
|
22
|
+
if (srcEnd - srcStart < 4) return changetype<string>(0);
|
|
16
23
|
if (JSON_MODE == JSONMode.SIMD) {
|
|
17
24
|
return deserializeString_SIMD(srcStart, srcEnd);
|
|
18
25
|
} else if (JSON_MODE == JSONMode.NAIVE) {
|
|
@@ -36,3 +43,33 @@ export function deserializeStringField<T extends string | null>(
|
|
|
36
43
|
return deserializeStringField_SWAR<T>(srcStart, srcEnd, dstObj, dstOffset);
|
|
37
44
|
}
|
|
38
45
|
}
|
|
46
|
+
|
|
47
|
+
export function deserializeStringFieldTrusted(
|
|
48
|
+
payloadStart: usize,
|
|
49
|
+
srcEnd: usize,
|
|
50
|
+
dstObj: usize,
|
|
51
|
+
dstOffset: usize = 0,
|
|
52
|
+
): usize {
|
|
53
|
+
if (JSON_MODE == JSONMode.SIMD) {
|
|
54
|
+
return deserializeStringFieldTrusted_SIMD(
|
|
55
|
+
payloadStart,
|
|
56
|
+
srcEnd,
|
|
57
|
+
dstObj,
|
|
58
|
+
dstOffset,
|
|
59
|
+
);
|
|
60
|
+
} else if (JSON_MODE == JSONMode.NAIVE) {
|
|
61
|
+
return deserializeStringFieldTrusted_NAIVE(
|
|
62
|
+
payloadStart,
|
|
63
|
+
srcEnd,
|
|
64
|
+
dstObj,
|
|
65
|
+
dstOffset,
|
|
66
|
+
);
|
|
67
|
+
} else {
|
|
68
|
+
return deserializeStringFieldTrusted_SWAR(
|
|
69
|
+
payloadStart,
|
|
70
|
+
srcEnd,
|
|
71
|
+
dstObj,
|
|
72
|
+
dstOffset,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -10,6 +10,7 @@ export function deserializeArbitraryArray(
|
|
|
10
10
|
dst || changetype<usize>(instantiate<JSON.Value[]>()),
|
|
11
11
|
);
|
|
12
12
|
// Skip the opening '[' and parse elements single-pass until the matching ']'.
|
|
13
|
-
parseArrayBody(out, srcStart + 2, srcEnd)
|
|
14
|
-
|
|
13
|
+
return parseArrayBody(out, srcStart + 2, srcEnd) != 0
|
|
14
|
+
? out
|
|
15
|
+
: changetype<JSON.Value[]>(0);
|
|
15
16
|
}
|
|
@@ -28,6 +28,7 @@ export function deserializeArrayArray<T extends unknown[][]>(
|
|
|
28
28
|
if (load<u16>(srcStart) == BRACKET_LEFT) {
|
|
29
29
|
const inner = instantiate<JSON.Value[]>();
|
|
30
30
|
srcStart = parseArrayBody(inner, srcStart + 2, srcEnd);
|
|
31
|
+
if (srcStart == 0) return changetype<T>(0);
|
|
31
32
|
// @ts-ignore: valueof<T> is JSON.Value[] in this branch
|
|
32
33
|
out.push(changetype<valueof<T>>(changetype<usize>(inner)));
|
|
33
34
|
} else {
|
|
@@ -6,13 +6,15 @@ import {
|
|
|
6
6
|
FALSE_WORD_U64,
|
|
7
7
|
TRUE_WORD_U64,
|
|
8
8
|
} from "../../../custom/chars";
|
|
9
|
+
import { markProductionParseError } from "../../error";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Strict boolean-array deserializer (`bool[]`, every JSON_MODE).
|
|
12
13
|
*
|
|
13
14
|
* Enforces RFC 8259 array structure: `[`-framed, single-comma separated, no
|
|
14
15
|
* leading / trailing / doubled commas, and each element must be exactly the
|
|
15
|
-
* literal `true` or `false`.
|
|
16
|
+
* literal `true` or `false`. Returns the shared failure sentinel on any
|
|
17
|
+
* deviation.
|
|
16
18
|
*
|
|
17
19
|
* The token check is SWAR-shaped: one `u64` load matches all four chars of
|
|
18
20
|
* `true`; `false` adds one `u16` load to confirm the trailing `e`.
|
|
@@ -27,12 +29,15 @@ export function deserializeBooleanArray<T extends boolean[]>(
|
|
|
27
29
|
);
|
|
28
30
|
out.length = 0; // dst may arrive pre-sized; re-parse from empty via push
|
|
29
31
|
|
|
30
|
-
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
31
32
|
while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
if (
|
|
34
|
+
srcStart >= srcEnd ||
|
|
35
|
+
load<u16>(srcStart) != BRACKET_LEFT ||
|
|
36
|
+
load<u16>(srcEnd - 2) != BRACKET_RIGHT
|
|
37
|
+
) {
|
|
38
|
+
markProductionParseError();
|
|
39
|
+
return changetype<T>(0);
|
|
40
|
+
}
|
|
36
41
|
srcStart += 2; // past '['
|
|
37
42
|
srcEnd -= 2; // before ']'
|
|
38
43
|
|
|
@@ -51,17 +56,22 @@ export function deserializeBooleanArray<T extends boolean[]>(
|
|
|
51
56
|
out.push(false);
|
|
52
57
|
srcStart += 10;
|
|
53
58
|
} else {
|
|
54
|
-
|
|
59
|
+
markProductionParseError();
|
|
60
|
+
return changetype<T>(0);
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
58
64
|
if (srcStart >= srcEnd) break;
|
|
59
|
-
if (load<u16>(srcStart) != COMMA)
|
|
60
|
-
|
|
65
|
+
if (load<u16>(srcStart) != COMMA) {
|
|
66
|
+
markProductionParseError();
|
|
67
|
+
return changetype<T>(0);
|
|
68
|
+
}
|
|
61
69
|
srcStart += 2;
|
|
62
70
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
63
|
-
if (srcStart >= srcEnd)
|
|
64
|
-
|
|
71
|
+
if (srcStart >= srcEnd) {
|
|
72
|
+
markProductionParseError();
|
|
73
|
+
return changetype<T>(0);
|
|
74
|
+
}
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
return out;
|
|
@@ -10,42 +10,22 @@ export function deserializeBoxArray<T extends JSON.Box<any>[]>(
|
|
|
10
10
|
const out = changetype<nonnull<T>>(
|
|
11
11
|
dst || changetype<usize>(instantiate<T>()),
|
|
12
12
|
);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
srcStart += 2;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return out;
|
|
28
|
-
} else {
|
|
29
|
-
let lastIndex: usize = 0;
|
|
30
|
-
while (srcStart < srcEnd) {
|
|
31
|
-
const code = load<u16>(srcStart);
|
|
32
|
-
if (code - 48 <= 9 || code == 45) {
|
|
33
|
-
lastIndex = srcStart;
|
|
34
|
-
srcStart += 2;
|
|
35
|
-
while (srcStart < srcEnd) {
|
|
36
|
-
const code = load<u16>(srcStart);
|
|
37
|
-
if (code == COMMA || code == BRACKET_RIGHT || isSpace(code)) {
|
|
38
|
-
out.push(JSON.__deserialize<valueof<T>>(lastIndex, srcStart));
|
|
39
|
-
// while (isSpace(load<u16>((srcStart += 2)))) {
|
|
40
|
-
// /* empty */
|
|
41
|
-
// }
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
srcStart += 2;
|
|
13
|
+
let lastIndex: usize = 0;
|
|
14
|
+
while (srcStart < srcEnd) {
|
|
15
|
+
const code = load<u16>(srcStart);
|
|
16
|
+
if (code - 48 <= 9 || code == 45) {
|
|
17
|
+
lastIndex = srcStart;
|
|
18
|
+
srcStart += 2;
|
|
19
|
+
while (srcStart < srcEnd) {
|
|
20
|
+
const code = load<u16>(srcStart);
|
|
21
|
+
if (code == COMMA || code == BRACKET_RIGHT || isSpace(code)) {
|
|
22
|
+
out.push(JSON.__deserialize<valueof<T>>(lastIndex, srcStart));
|
|
23
|
+
break;
|
|
45
24
|
}
|
|
25
|
+
srcStart += 2;
|
|
46
26
|
}
|
|
47
|
-
srcStart += 2;
|
|
48
27
|
}
|
|
28
|
+
srcStart += 2;
|
|
49
29
|
}
|
|
50
30
|
return out;
|
|
51
31
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isSpace } from "../../../util";
|
|
2
2
|
import { COMMA, BRACKET_LEFT, BRACKET_RIGHT } from "../../../custom/chars";
|
|
3
3
|
import { JSON } from "../../..";
|
|
4
|
+
import { markProductionParseError } from "../../error";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Strict float-array deserializer (`f64[]` / `f32[]`).
|
|
@@ -8,8 +9,8 @@ import { JSON } from "../../..";
|
|
|
8
9
|
* Enforces RFC 8259 array structure: `[`-framed, single-comma separated, no
|
|
9
10
|
* leading / trailing / doubled commas, and no garbage between values. Each
|
|
10
11
|
* element token is validated by `deserializeFloat_NAIVE` (via `JSON.__deserialize`),
|
|
11
|
-
* so malformed numbers like `0e` / `-01` / `1.` are rejected here too.
|
|
12
|
-
* any deviation.
|
|
12
|
+
* so malformed numbers like `0e` / `-01` / `1.` are rejected here too. Returns
|
|
13
|
+
* the shared failure sentinel on any deviation.
|
|
13
14
|
*/
|
|
14
15
|
export function deserializeFloatArray_NAIVE<T extends number[]>(
|
|
15
16
|
srcStart: usize,
|
|
@@ -24,12 +25,15 @@ export function deserializeFloatArray_NAIVE<T extends number[]>(
|
|
|
24
25
|
out.length = 0;
|
|
25
26
|
|
|
26
27
|
// Trim surrounding whitespace and require the enclosing brackets.
|
|
27
|
-
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
28
28
|
while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
if (
|
|
30
|
+
srcStart >= srcEnd ||
|
|
31
|
+
load<u16>(srcStart) != BRACKET_LEFT ||
|
|
32
|
+
load<u16>(srcEnd - 2) != BRACKET_RIGHT
|
|
33
|
+
) {
|
|
34
|
+
markProductionParseError();
|
|
35
|
+
return changetype<T>(0);
|
|
36
|
+
}
|
|
33
37
|
srcStart += 2; // past '['
|
|
34
38
|
srcEnd -= 2; // before ']'
|
|
35
39
|
|
|
@@ -45,18 +49,24 @@ export function deserializeFloatArray_NAIVE<T extends number[]>(
|
|
|
45
49
|
if (c == COMMA || isSpace(c)) break;
|
|
46
50
|
srcStart += 2;
|
|
47
51
|
}
|
|
48
|
-
if (srcStart == tokenStart)
|
|
49
|
-
|
|
52
|
+
if (srcStart == tokenStart) {
|
|
53
|
+
markProductionParseError();
|
|
54
|
+
return changetype<T>(0);
|
|
55
|
+
}
|
|
50
56
|
out.push(JSON.__deserialize<valueof<T>>(tokenStart, srcStart));
|
|
51
57
|
|
|
52
58
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
53
59
|
if (srcStart >= srcEnd) break; // end of array body
|
|
54
|
-
if (load<u16>(srcStart) != COMMA)
|
|
55
|
-
|
|
60
|
+
if (load<u16>(srcStart) != COMMA) {
|
|
61
|
+
markProductionParseError();
|
|
62
|
+
return changetype<T>(0);
|
|
63
|
+
}
|
|
56
64
|
srcStart += 2; // past ','
|
|
57
65
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
58
|
-
if (srcStart >= srcEnd)
|
|
59
|
-
|
|
66
|
+
if (srcStart >= srcEnd) {
|
|
67
|
+
markProductionParseError();
|
|
68
|
+
return changetype<T>(0);
|
|
69
|
+
}
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
return out;
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import { atoi, isSpace } from "../../../util";
|
|
2
2
|
import { COMMA, BRACKET_LEFT, BRACKET_RIGHT } from "../../../custom/chars";
|
|
3
|
+
import { markProductionParseError } from "../../error";
|
|
3
4
|
|
|
4
5
|
// Strict RFC 8259 integer-token check over [start, end): optional minus (signed
|
|
5
6
|
// types only), then a lone `0` or [1-9] digits - no leading zeros, fraction,
|
|
6
|
-
// exponent, or trailing garbage.
|
|
7
|
-
function validateJSONInteger(start: usize, end: usize, signed: bool):
|
|
7
|
+
// exponent, or trailing garbage. Returns false otherwise.
|
|
8
|
+
function validateJSONInteger(start: usize, end: usize, signed: bool): bool {
|
|
8
9
|
let ptr = start;
|
|
9
10
|
if (ptr < end && load<u16>(ptr) == 45) {
|
|
10
|
-
if (!signed)
|
|
11
|
+
if (!signed) return false;
|
|
11
12
|
ptr += 2;
|
|
12
13
|
}
|
|
13
|
-
if (ptr >= end)
|
|
14
|
+
if (ptr >= end) return false;
|
|
14
15
|
const first = load<u16>(ptr);
|
|
15
16
|
if (first == 48) {
|
|
16
17
|
ptr += 2;
|
|
17
|
-
if (ptr < end && <u32>(load<u16>(ptr) - 48) <= 9)
|
|
18
|
-
throw new Error("Invalid JSON number: leading zero");
|
|
18
|
+
if (ptr < end && <u32>(load<u16>(ptr) - 48) <= 9) return false;
|
|
19
19
|
} else if (<u32>(first - 48) <= 9) {
|
|
20
20
|
ptr += 2;
|
|
21
21
|
while (ptr < end && <u32>(load<u16>(ptr) - 48) <= 9) ptr += 2;
|
|
22
22
|
} else {
|
|
23
|
-
|
|
23
|
+
return false;
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
return ptr == end;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -30,7 +30,8 @@ function validateJSONInteger(start: usize, end: usize, signed: bool): void {
|
|
|
30
30
|
*
|
|
31
31
|
* Enforces RFC 8259 array structure: `[`-framed, single-comma separated, no
|
|
32
32
|
* leading / trailing / doubled commas, and each element must be a valid JSON
|
|
33
|
-
* integer (no leading zeros, fraction, or exponent).
|
|
33
|
+
* integer (no leading zeros, fraction, or exponent). Returns the shared failure
|
|
34
|
+
* sentinel on any deviation.
|
|
34
35
|
*/
|
|
35
36
|
export function deserializeIntegerArray_NAIVE<T extends number[]>(
|
|
36
37
|
srcStart: usize,
|
|
@@ -42,12 +43,15 @@ export function deserializeIntegerArray_NAIVE<T extends number[]>(
|
|
|
42
43
|
);
|
|
43
44
|
out.length = 0; // dst may arrive pre-sized; re-parse from empty via push
|
|
44
45
|
|
|
45
|
-
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
46
46
|
while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
if (
|
|
48
|
+
srcStart >= srcEnd ||
|
|
49
|
+
load<u16>(srcStart) != BRACKET_LEFT ||
|
|
50
|
+
load<u16>(srcEnd - 2) != BRACKET_RIGHT
|
|
51
|
+
) {
|
|
52
|
+
markProductionParseError();
|
|
53
|
+
return changetype<T>(0);
|
|
54
|
+
}
|
|
51
55
|
srcStart += 2; // past '['
|
|
52
56
|
srcEnd -= 2; // before ']'
|
|
53
57
|
|
|
@@ -62,19 +66,27 @@ export function deserializeIntegerArray_NAIVE<T extends number[]>(
|
|
|
62
66
|
if (c == COMMA || isSpace(c)) break;
|
|
63
67
|
srcStart += 2;
|
|
64
68
|
}
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
if (
|
|
70
|
+
srcStart == tokenStart ||
|
|
71
|
+
!validateJSONInteger(tokenStart, srcStart, signed)
|
|
72
|
+
) {
|
|
73
|
+
markProductionParseError();
|
|
74
|
+
return changetype<T>(0);
|
|
75
|
+
}
|
|
68
76
|
out.push(atoi<valueof<T>>(tokenStart, srcStart));
|
|
69
77
|
|
|
70
78
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
71
79
|
if (srcStart >= srcEnd) break;
|
|
72
|
-
if (load<u16>(srcStart) != COMMA)
|
|
73
|
-
|
|
80
|
+
if (load<u16>(srcStart) != COMMA) {
|
|
81
|
+
markProductionParseError();
|
|
82
|
+
return changetype<T>(0);
|
|
83
|
+
}
|
|
74
84
|
srcStart += 2;
|
|
75
85
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
76
|
-
if (srcStart >= srcEnd)
|
|
77
|
-
|
|
86
|
+
if (srcStart >= srcEnd) {
|
|
87
|
+
markProductionParseError();
|
|
88
|
+
return changetype<T>(0);
|
|
89
|
+
}
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
return out;
|
|
@@ -34,6 +34,7 @@ export function deserializeObjectArray<T extends unknown[]>(
|
|
|
34
34
|
if (load<u16>(srcStart) == BRACE_LEFT) {
|
|
35
35
|
const obj = new JSON.Obj();
|
|
36
36
|
srcStart = parseObjectBody(obj, srcStart + 2, srcEnd);
|
|
37
|
+
if (srcStart == 0) return changetype<T>(0);
|
|
37
38
|
out.push(changetype<valueof<T>>(changetype<usize>(obj)));
|
|
38
39
|
} else {
|
|
39
40
|
srcStart += 2;
|