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
|
@@ -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
|
|
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 (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3
|
-
if (
|
|
4
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
if (load<u16>(
|
|
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>(
|
|
45
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
if (
|
|
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
|
|
78
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
258
|
+
key,
|
|
91
259
|
changetype<valueof<T>>(changetype<usize>(val)),
|
|
92
260
|
);
|
|
93
|
-
srcStart =
|
|
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
|
-
|
|
370
|
+
key,
|
|
100
371
|
JSON.__deserialize<valueof<T>>(srcStart, valueEnd),
|
|
101
372
|
);
|
|
102
373
|
srcStart = valueEnd;
|
|
103
374
|
}
|
|
104
375
|
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
403
|
+
if (srcStart < srcEnd && load<u16>(srcStart) != QUOTE)
|
|
404
|
+
srcStart = skipMapWhitespace(srcStart, srcEnd);
|
|
111
405
|
continue;
|
|
112
406
|
}
|
|
113
|
-
if (code == BRACE_RIGHT)
|
|
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
|
-
|
|
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
|
}
|