json-as 1.0.4 → 1.0.6
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 +10 -1
- package/README.md +32 -22
- package/SECURITY.md +2 -1
- package/assembly/__benches__/large.bench.ts +3 -0
- package/assembly/__benches__/vec3.bench.ts +3 -0
- package/assembly/__tests__/arbitrary.spec.ts +3 -3
- package/assembly/__tests__/array.spec.ts +5 -8
- package/assembly/__tests__/box.spec.ts +10 -20
- package/assembly/__tests__/custom.spec.ts +7 -6
- package/assembly/__tests__/date.spec.ts +4 -6
- package/assembly/__tests__/float.spec.ts +3 -3
- package/assembly/__tests__/map.spec.ts +1 -1
- package/assembly/__tests__/raw.spec.ts +3 -3
- package/assembly/__tests__/struct.spec.ts +25 -14
- package/assembly/deserialize/simd/string.ts +1 -2
- package/assembly/deserialize/simple/array/struct.ts +2 -3
- package/assembly/deserialize/simple/array.ts +1 -1
- package/assembly/deserialize/simple/bool.ts +1 -1
- package/assembly/deserialize/simple/map.ts +3 -4
- package/assembly/deserialize/simple/object.ts +2 -3
- package/assembly/deserialize/simple/raw.ts +1 -1
- package/assembly/deserialize/simple/struct.ts +2 -3
- package/assembly/index.ts +26 -10
- package/assembly/serialize/simd/string.ts +1 -0
- package/assembly/serialize/simple/integer.ts +1 -1
- package/assembly/serialize/simple/object.ts +6 -6
- package/assembly/test.ts +26 -24
- package/bench.js +2 -4
- package/index.ts +1 -1
- package/lib/as-bs.ts +4 -13
- package/package.json +4 -2
- package/run-tests.sh +1 -1
- package/transform/lib/index.js +61 -63
- package/transform/lib/index.js.map +1 -1
- package/transform/src/index.ts +73 -138
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 2025-05-11 - 1.0.5
|
|
4
|
+
|
|
5
|
+
- feat: add sanity checks for badly formatted strings
|
|
6
|
+
- fix: [#120](https://github.com/JairusSW/json-as/issues/120) handle empty `JSON.Obj` serialization
|
|
7
|
+
- feat: add SIMD optimization if SIMD is enabled by user
|
|
8
|
+
- fix: handle structs with nullable array as property [#123](https://github.com/JairusSW/json-as/pull/123)
|
|
9
|
+
- fix: struct serialization from writing to incorrect parts of memory when parsing nested structs [#125](https://github.com/JairusSW/json-as/pull/125)
|
|
10
|
+
- chore: add two new contributors
|
|
11
|
+
|
|
3
12
|
## 2025-04-07 - 1.0.4
|
|
4
13
|
|
|
5
14
|
- fix: paths must be resolved as POSIX in order to be valid TypeScript imports [#116](https://github.com/JairusSW/json-as/issues/116)
|
|
@@ -154,4 +163,4 @@
|
|
|
154
163
|
- feat: reduce memory usage so that it is viable for low-memory environments
|
|
155
164
|
- feat: write to a central buffer and reduce memory overhead
|
|
156
165
|
- feat: rewrite the transform to properly resolve schemas and link them together
|
|
157
|
-
- feat: pre-allocate and compute the minimum size of a schema to avoid memory out of range errors
|
|
166
|
+
- feat: pre-allocate and compute the minimum size of a schema to avoid memory out of range errors
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<
|
|
1
|
+
<h6 align="center">
|
|
2
2
|
<pre>
|
|
3
3
|
<span style="font-size: 0.8em;"> ██ ███████ ██████ ███ ██ █████ ███████
|
|
4
4
|
██ ██ ██ ██ ████ ██ ██ ██ ██
|
|
@@ -6,26 +6,14 @@
|
|
|
6
6
|
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
7
7
|
█████ ███████ ██████ ██ ████ ██ ██ ███████
|
|
8
8
|
</span>
|
|
9
|
-
AssemblyScript - v1.0.
|
|
9
|
+
AssemblyScript - v1.0.6
|
|
10
10
|
</pre>
|
|
11
|
-
</
|
|
11
|
+
</h6>
|
|
12
12
|
|
|
13
13
|
## 📝 About
|
|
14
14
|
|
|
15
15
|
JSON is the de-facto serialization format of modern web applications, but its serialization and deserialization remain a significant performance bottleneck, especially at scale. Traditional parsing approaches are computationally expensive, adding unnecessary overhead to both clients and servers. This library is designed to mitigate this by leveraging SIMD acceleration and highly optimized transformations.
|
|
16
16
|
|
|
17
|
-
## 🔭 What's new
|
|
18
|
-
|
|
19
|
-
🔹Major performance improvements and addition of SIMD
|
|
20
|
-
|
|
21
|
-
🔹Near zero-growth allocation design and low overhead
|
|
22
|
-
|
|
23
|
-
🔹Support for custom serializer and deserializers
|
|
24
|
-
|
|
25
|
-
🔹Fixes to many, many, bugs and edge cases
|
|
26
|
-
|
|
27
|
-
🔹Support for dynamic objects, arrays, arbitrary values, and raw types
|
|
28
|
-
|
|
29
17
|
## 📚 Contents
|
|
30
18
|
|
|
31
19
|
- [Installation](#-installation)
|
|
@@ -69,6 +57,7 @@ If you'd like to see the code that the transform generates, run the build step w
|
|
|
69
57
|
```typescript
|
|
70
58
|
import { JSON } from "json-as";
|
|
71
59
|
|
|
60
|
+
|
|
72
61
|
@json
|
|
73
62
|
class Vec3 {
|
|
74
63
|
x: f32 = 0.0;
|
|
@@ -76,8 +65,10 @@ class Vec3 {
|
|
|
76
65
|
z: f32 = 0.0;
|
|
77
66
|
}
|
|
78
67
|
|
|
68
|
+
|
|
79
69
|
@json
|
|
80
70
|
class Player {
|
|
71
|
+
|
|
81
72
|
@alias("first name")
|
|
82
73
|
firstName!: string;
|
|
83
74
|
lastName!: string;
|
|
@@ -85,6 +76,8 @@ class Player {
|
|
|
85
76
|
// Drop in a code block, function, or expression that evaluates to a boolean
|
|
86
77
|
@omitif((self: Player) => self.age < 18)
|
|
87
78
|
age!: i32;
|
|
79
|
+
|
|
80
|
+
|
|
88
81
|
@omitnull()
|
|
89
82
|
pos!: Vec3 | null;
|
|
90
83
|
isVerified!: boolean;
|
|
@@ -101,7 +94,7 @@ const player: Player = {
|
|
|
101
94
|
z: 8.3,
|
|
102
95
|
},
|
|
103
96
|
isVerified: true,
|
|
104
|
-
}
|
|
97
|
+
};
|
|
105
98
|
|
|
106
99
|
const serialized = JSON.stringify<Player>(player);
|
|
107
100
|
const deserialized = JSON.parse<Player>(serialized);
|
|
@@ -121,9 +114,12 @@ This library allows selective omission of fields during serialization using the
|
|
|
121
114
|
This decorator excludes a field from serialization entirely.
|
|
122
115
|
|
|
123
116
|
```typescript
|
|
117
|
+
|
|
124
118
|
@json
|
|
125
119
|
class Example {
|
|
126
120
|
name!: string;
|
|
121
|
+
|
|
122
|
+
|
|
127
123
|
@omit
|
|
128
124
|
SSN!: string;
|
|
129
125
|
}
|
|
@@ -140,9 +136,12 @@ console.log(JSON.stringify(obj)); // { "name": "Jairus" }
|
|
|
140
136
|
This decorator omits a field only if its value is null.
|
|
141
137
|
|
|
142
138
|
```typescript
|
|
139
|
+
|
|
143
140
|
@json
|
|
144
141
|
class Example {
|
|
145
142
|
name!: string;
|
|
143
|
+
|
|
144
|
+
|
|
146
145
|
@omitnull()
|
|
147
146
|
optionalField!: string | null;
|
|
148
147
|
}
|
|
@@ -159,9 +158,12 @@ console.log(JSON.stringify(obj)); // { "name": "Jairus" }
|
|
|
159
158
|
This decorator omits a field based on a custom predicate function.
|
|
160
159
|
|
|
161
160
|
```typescript
|
|
161
|
+
|
|
162
162
|
@json
|
|
163
163
|
class Example {
|
|
164
164
|
name!: string;
|
|
165
|
+
|
|
166
|
+
|
|
165
167
|
@omitif((self: Example) => self.age <= 18)
|
|
166
168
|
age!: number;
|
|
167
169
|
}
|
|
@@ -186,6 +188,7 @@ AssemblyScript doesn't support using nullable primitive types, so instead, json-
|
|
|
186
188
|
For example, this schema won't compile in AssemblyScript:
|
|
187
189
|
|
|
188
190
|
```typescript
|
|
191
|
+
|
|
189
192
|
@json
|
|
190
193
|
class Person {
|
|
191
194
|
name!: string;
|
|
@@ -196,6 +199,7 @@ class Person {
|
|
|
196
199
|
Instead, use `JSON.Box` to allow nullable primitives:
|
|
197
200
|
|
|
198
201
|
```typescript
|
|
202
|
+
|
|
199
203
|
@json
|
|
200
204
|
class Person {
|
|
201
205
|
name: string;
|
|
@@ -239,11 +243,12 @@ When dealing with an object with an unknown structure, use the `JSON.Obj` type
|
|
|
239
243
|
const obj = JSON.parse<JSON.Obj>('{"a":3.14,"b":true,"c":[1,2,3],"d":{"x":1,"y":2,"z":3}}');
|
|
240
244
|
|
|
241
245
|
console.log("Keys: " + obj.keys().join(" ")); // a b c d
|
|
242
|
-
console.log(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
246
|
+
console.log(
|
|
247
|
+
"Values: " +
|
|
248
|
+
obj
|
|
249
|
+
.values()
|
|
250
|
+
.map<string>((v) => JSON.stringify(v))
|
|
251
|
+
.join(" "),
|
|
247
252
|
); // 3.14 true [1,2,3] {"x":1,"y":2,"z":3}
|
|
248
253
|
|
|
249
254
|
const y = obj.get("d")!.get<JSON.Obj>().get("y")!;
|
|
@@ -257,6 +262,7 @@ More often, objects will be completely statically typed except for one or two va
|
|
|
257
262
|
In such cases, `JSON.Value` can be used to handle fields that may hold different types at runtime.
|
|
258
263
|
|
|
259
264
|
```typescript
|
|
265
|
+
|
|
260
266
|
@json
|
|
261
267
|
class DynamicObj {
|
|
262
268
|
id: i32 = 0;
|
|
@@ -313,6 +319,7 @@ Here's an example of creating a custom data type called `Point` which serializes
|
|
|
313
319
|
```typescript
|
|
314
320
|
import { bytes } from "json-as/assembly/util";
|
|
315
321
|
|
|
322
|
+
|
|
316
323
|
@json
|
|
317
324
|
class Point {
|
|
318
325
|
x: f64 = 0.0;
|
|
@@ -322,11 +329,13 @@ class Point {
|
|
|
322
329
|
this.y = y;
|
|
323
330
|
}
|
|
324
331
|
|
|
332
|
+
|
|
325
333
|
@serializer
|
|
326
334
|
serializer(self: Point): string {
|
|
327
335
|
return `(${self.x},${self.y})`;
|
|
328
336
|
}
|
|
329
337
|
|
|
338
|
+
|
|
330
339
|
@deserializer
|
|
331
340
|
deserializer(data: string): Point {
|
|
332
341
|
const dataSize = bytes(data);
|
|
@@ -403,6 +412,7 @@ These benchmarks compare this library to JavaScript's native `JSON.stringify` an
|
|
|
403
412
|
- JSON-AS consistently outperforms JavaScript's native implementation.
|
|
404
413
|
|
|
405
414
|
- **Serialization Speed:**
|
|
415
|
+
|
|
406
416
|
- JSON-AS achieves speeds up to `2,133 MB/s`, significantly faster than JavaScript's peak of `1,416 MB/s`.
|
|
407
417
|
- Large objects see the biggest improvement, with JSON-AS at `2,074 MB/s` vs. JavaScript’s `749.7 MB/s`.
|
|
408
418
|
|
|
@@ -457,4 +467,4 @@ Please send all issues to [GitHub Issues](https://github.com/JairusSW/json-as/is
|
|
|
457
467
|
- **Email:** Send me inquiries, questions, or requests at [me@jairus.dev](mailto:me@jairus.dev)
|
|
458
468
|
- **GitHub:** Visit the official GitHub repository [Here](https://github.com/JairusSW/json-as)
|
|
459
469
|
- **Website:** Visit my official website at [jairus.dev](https://jairus.dev/)
|
|
460
|
-
- **Discord:** Contact me at [My Discord](https://discord.com/users/600700584038760448) or on the [AssemblyScript Discord Server](https://discord.gg/assemblyscript/)
|
|
470
|
+
- **Discord:** Contact me at [My Discord](https://discord.com/users/600700584038760448) or on the [AssemblyScript Discord Server](https://discord.gg/assemblyscript/)
|
package/SECURITY.md
CHANGED
|
@@ -18,7 +18,8 @@ If you believe you have discovered a security vulnerability in this project, ple
|
|
|
18
18
|
|
|
19
19
|
3. **Acknowledgment**: Once your report is received, we will acknowledge it within 48 hours. You will then receive an update on the progress of the investigation and a timeline for a potential fix.
|
|
20
20
|
|
|
21
|
-
4. **Resolution Timeline**:
|
|
21
|
+
4. **Resolution Timeline**:
|
|
22
|
+
|
|
22
23
|
- Critical vulnerabilities will be prioritized, and we aim to issue a patch within 7 business days.
|
|
23
24
|
- Non-critical vulnerabilities will be evaluated and addressed in the next stable release.
|
|
24
25
|
- If the vulnerability is accepted, a fix will be issued, and a security advisory will be published.
|
|
@@ -8,6 +8,7 @@ class Vec3 {
|
|
|
8
8
|
public y!: i32;
|
|
9
9
|
public z!: i32;
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
@inline __SERIALIZE(ptr: usize): void {
|
|
12
13
|
bs.proposeSize(98);
|
|
13
14
|
store<u64>(bs.offset, 9570664606466171, 0); // {"x"
|
|
@@ -26,10 +27,12 @@ class Vec3 {
|
|
|
26
27
|
bs.offset += 2;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
|
|
29
31
|
@inline __INITIALIZE(): this {
|
|
30
32
|
return this;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
|
|
33
36
|
@inline __DESERIALIZE(keyStart: usize, keyEnd: usize, valStart: usize, valEnd: usize, ptr: usize): void {
|
|
34
37
|
switch (load<u16>(keyStart)) {
|
|
35
38
|
case 120: {
|
|
@@ -8,6 +8,7 @@ class Vec3 {
|
|
|
8
8
|
public y!: i32;
|
|
9
9
|
public z!: i32;
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
@inline __SERIALIZE(ptr: usize): void {
|
|
12
13
|
bs.proposeSize(98);
|
|
13
14
|
store<u64>(bs.offset, 9570664606466171, 0); // {"x"
|
|
@@ -26,10 +27,12 @@ class Vec3 {
|
|
|
26
27
|
bs.offset += 2;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
|
|
29
31
|
@inline __INITIALIZE(): this {
|
|
30
32
|
return this;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
|
|
33
36
|
@inline __DESERIALIZE(keyStart: usize, keyEnd: usize, valStart: usize, valEnd: usize, ptr: usize): void {
|
|
34
37
|
switch (load<u16>(keyStart)) {
|
|
35
38
|
case 120: {
|
|
@@ -3,7 +3,7 @@ import { describe, expect } from "./lib";
|
|
|
3
3
|
import { Vec3 } from "./types";
|
|
4
4
|
|
|
5
5
|
describe("Should serialize arbitrary types", () => {
|
|
6
|
-
expect(JSON.stringify(JSON.Value.from("hello world"))).toBe("
|
|
6
|
+
expect(JSON.stringify(JSON.Value.from("hello world"))).toBe('"hello world"');
|
|
7
7
|
expect(JSON.stringify(JSON.Value.from(0))).toBe("0");
|
|
8
8
|
expect(JSON.stringify(JSON.Value.from(true))).toBe("true");
|
|
9
9
|
expect(JSON.stringify(JSON.Value.from(new Vec3()))).toBe('{"x":1.0,"y":2.0,"z":3.0}');
|
|
@@ -11,9 +11,9 @@ describe("Should serialize arbitrary types", () => {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
describe("Should deserialize arbitrary types", () => {
|
|
14
|
-
expect(JSON.parse<JSON.Value>("
|
|
14
|
+
expect(JSON.parse<JSON.Value>('"hello world"').get<string>()).toBe("hello world");
|
|
15
15
|
expect(JSON.parse<JSON.Value>("0.0").toString()).toBe("0.0");
|
|
16
16
|
expect(JSON.parse<JSON.Value>("true").toString()).toBe("true");
|
|
17
17
|
expect(JSON.stringify(JSON.parse<JSON.Value>('{"x":1.0,"y":2.0,"z":3.0}'))).toBe('{"x":1.0,"y":2.0,"z":3.0}');
|
|
18
18
|
expect(JSON.stringify(JSON.parse<JSON.Value[]>('["string",true,3.14,{"x":1.0,"y":2.0,"z":3.0},[1.0,2.0,3,true]]'))).toBe('["string",true,3.14,{"x":1.0,"y":2.0,"z":3.0},[1.0,2.0,3.0,true]]');
|
|
19
|
-
});
|
|
19
|
+
});
|
|
@@ -59,8 +59,8 @@ describe("Should serialize object arrays", () => {
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
describe("Should deserialize integer arrays", () => {
|
|
62
|
-
expect(JSON.stringify(JSON.parse<u32[]>("[0,100,101]"))).toBe(
|
|
63
|
-
expect(JSON.stringify(JSON.parse<u64[]>("[0,100,101]"))).toBe(
|
|
62
|
+
expect(JSON.stringify(JSON.parse<u32[]>("[0,100,101]"))).toBe("[0,100,101]");
|
|
63
|
+
expect(JSON.stringify(JSON.parse<u64[]>("[0,100,101]"))).toBe("[0,100,101]");
|
|
64
64
|
expect(JSON.stringify(JSON.parse<i32[]>("[0,100,101,-100,-101]"))).toBe("[0,100,101,-100,-101]");
|
|
65
65
|
expect(JSON.stringify(JSON.parse<i64[]>("[0,100,101,-100,-101]"))).toBe("[0,100,101,-100,-101]");
|
|
66
66
|
});
|
|
@@ -76,7 +76,7 @@ describe("Should deserialize boolean arrays", () => {
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
describe("Should deserialize string arrays", () => {
|
|
79
|
-
expect(JSON.stringify(JSON.parse<string[]>(
|
|
79
|
+
expect(JSON.stringify(JSON.parse<string[]>('["string \\"with random spa\\nces and \\nnewlines\\n\\n\\n"]'))).toBe('["string \\"with random spa\\nces and \\nnewlines\\n\\n\\n"]');
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
describe("Should deserialize nested integer arrays", () => {
|
|
@@ -93,13 +93,10 @@ describe("Should deserialize nested boolean arrays", () => {
|
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
describe("Should deserialize object arrays", () => {
|
|
96
|
-
expect(
|
|
97
|
-
JSON.stringify(JSON.parse<Vec3[]>(
|
|
98
|
-
'[{"x":3.4,"y":1.2,"z":8.3},{"x":3.4,"y":-2.1,"z":9.3}]'
|
|
99
|
-
)
|
|
100
|
-
)).toBe('[{"x":3.4,"y":1.2,"z":8.3},{"x":3.4,"y":-2.1,"z":9.3}]');
|
|
96
|
+
expect(JSON.stringify(JSON.parse<Vec3[]>('[{"x":3.4,"y":1.2,"z":8.3},{"x":3.4,"y":-2.1,"z":9.3}]'))).toBe('[{"x":3.4,"y":1.2,"z":8.3},{"x":3.4,"y":-2.1,"z":9.3}]');
|
|
101
97
|
});
|
|
102
98
|
|
|
99
|
+
|
|
103
100
|
@json
|
|
104
101
|
class Vec3 {
|
|
105
102
|
x: f64 = 0.0;
|
|
@@ -2,36 +2,26 @@ import { JSON } from "..";
|
|
|
2
2
|
import { describe, expect } from "./lib";
|
|
3
3
|
|
|
4
4
|
describe("Should serialize JSON.Box<T>", () => {
|
|
5
|
-
expect(JSON.stringify<JSON.Box<i32> | null>(null))
|
|
6
|
-
.toBe("null");
|
|
5
|
+
expect(JSON.stringify<JSON.Box<i32> | null>(null)).toBe("null");
|
|
7
6
|
|
|
8
|
-
expect(JSON.stringify<JSON.Box<i32> | null>(new JSON.Box<i32>(0)))
|
|
9
|
-
.toBe("0");
|
|
7
|
+
expect(JSON.stringify<JSON.Box<i32> | null>(new JSON.Box<i32>(0))).toBe("0");
|
|
10
8
|
|
|
11
|
-
expect(JSON.stringify<JSON.Box<i32> | null>(new JSON.Box<i32>(1)))
|
|
12
|
-
.toBe("1");
|
|
9
|
+
expect(JSON.stringify<JSON.Box<i32> | null>(new JSON.Box<i32>(1))).toBe("1");
|
|
13
10
|
|
|
14
|
-
expect(JSON.stringify<JSON.Box<boolean> | null>(new JSON.Box<boolean>(false)))
|
|
15
|
-
.toBe("false");
|
|
11
|
+
expect(JSON.stringify<JSON.Box<boolean> | null>(new JSON.Box<boolean>(false))).toBe("false");
|
|
16
12
|
|
|
17
|
-
expect(JSON.stringify<JSON.Box<boolean> | null>(new JSON.Box<boolean>(true)))
|
|
18
|
-
.toBe("true");
|
|
13
|
+
expect(JSON.stringify<JSON.Box<boolean> | null>(new JSON.Box<boolean>(true))).toBe("true");
|
|
19
14
|
});
|
|
20
15
|
|
|
21
16
|
// This is somewhat clumsy to use. Perhaps I can redesign it or use some transform to make it more transparent.
|
|
22
17
|
describe("Should deserialize JSON.Box<T>", () => {
|
|
23
|
-
expect((JSON.parse<JSON.Box<i32> | null>("null") == null).toString())
|
|
24
|
-
.toBe("true");
|
|
18
|
+
expect((JSON.parse<JSON.Box<i32> | null>("null") == null).toString()).toBe("true");
|
|
25
19
|
|
|
26
|
-
expect(JSON.parse<JSON.Box<i32> | null>("0")!.value.toString())
|
|
27
|
-
.toBe("0");
|
|
20
|
+
expect(JSON.parse<JSON.Box<i32> | null>("0")!.value.toString()).toBe("0");
|
|
28
21
|
|
|
29
|
-
expect(JSON.parse<JSON.Box<i32> | null>("1")!.value.toString())
|
|
30
|
-
.toBe("1");
|
|
22
|
+
expect(JSON.parse<JSON.Box<i32> | null>("1")!.value.toString()).toBe("1");
|
|
31
23
|
|
|
32
|
-
expect(JSON.parse<JSON.Box<boolean> | null>("false")!.value.toString())
|
|
33
|
-
.toBe("false");
|
|
24
|
+
expect(JSON.parse<JSON.Box<boolean> | null>("false")!.value.toString()).toBe("false");
|
|
34
25
|
|
|
35
|
-
expect(JSON.parse<JSON.Box<boolean> | null>("true")!.value.toString())
|
|
36
|
-
.toBe("true");
|
|
26
|
+
expect(JSON.parse<JSON.Box<boolean> | null>("true")!.value.toString()).toBe("true");
|
|
37
27
|
});
|
|
@@ -2,6 +2,7 @@ import { JSON } from "..";
|
|
|
2
2
|
import { describe, expect } from "./lib";
|
|
3
3
|
import { bytes } from "../util";
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
@json
|
|
6
7
|
class Point {
|
|
7
8
|
x: f64 = 0.0;
|
|
@@ -10,10 +11,14 @@ class Point {
|
|
|
10
11
|
this.x = x;
|
|
11
12
|
this.y = y;
|
|
12
13
|
}
|
|
14
|
+
|
|
15
|
+
|
|
13
16
|
@serializer
|
|
14
17
|
serializer(self: Point): string {
|
|
15
18
|
return `(${self.x},${self.y})`;
|
|
16
19
|
}
|
|
20
|
+
|
|
21
|
+
|
|
17
22
|
@deserializer
|
|
18
23
|
deserializer(data: string): Point {
|
|
19
24
|
const dataSize = bytes(data);
|
|
@@ -23,16 +28,12 @@ class Point {
|
|
|
23
28
|
const x = data.slice(1, c);
|
|
24
29
|
const y = data.slice(c + 1, data.length - 1);
|
|
25
30
|
|
|
26
|
-
return new Point(
|
|
27
|
-
f64.parse(x),
|
|
28
|
-
f64.parse(y)
|
|
29
|
-
);
|
|
31
|
+
return new Point(f64.parse(x), f64.parse(y));
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
|
|
34
35
|
describe("Should serialize using custom serializers", () => {
|
|
35
|
-
expect(JSON.stringify<Point>(new Point(1,2))).toBe("(1.0,2.0)");
|
|
36
|
+
expect(JSON.stringify<Point>(new Point(1, 2))).toBe("(1.0,2.0)");
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
describe("Should deserialize using custom deserializers", () => {
|
|
@@ -2,10 +2,8 @@ import { JSON } from "..";
|
|
|
2
2
|
import { describe, expect } from "./lib";
|
|
3
3
|
|
|
4
4
|
describe("Should serialize Date", () => {
|
|
5
|
-
expect(JSON.stringify<Date>(new Date(0)))
|
|
6
|
-
|
|
7
|
-
expect(JSON.stringify<Date>(new Date(1738618120525)))
|
|
8
|
-
.toBe('"2025-02-03T21:28:40.525Z"');
|
|
5
|
+
expect(JSON.stringify<Date>(new Date(0))).toBe('"1970-01-01T00:00:00.000Z"');
|
|
6
|
+
expect(JSON.stringify<Date>(new Date(1738618120525))).toBe('"2025-02-03T21:28:40.525Z"');
|
|
9
7
|
});
|
|
10
8
|
|
|
11
9
|
describe("Should deserialize booleans", () => {
|
|
@@ -17,7 +15,7 @@ describe("Should deserialize booleans", () => {
|
|
|
17
15
|
// console.log("Minutes: " + date.getUTCMinutes().toString());
|
|
18
16
|
// console.log("Seconds: " + date.getUTCSeconds().toString());
|
|
19
17
|
// console.log("Milliseconds: " + date.getUTCMilliseconds().toString());
|
|
20
|
-
|
|
18
|
+
|
|
21
19
|
const date1 = JSON.parse<Date>('"1970-01-01T00:00:00.000Z"');
|
|
22
20
|
expect(date1.getUTCFullYear().toString()).toBe("1970");
|
|
23
21
|
expect(date1.getUTCMonth().toString()).toBe("0");
|
|
@@ -34,5 +32,5 @@ describe("Should deserialize booleans", () => {
|
|
|
34
32
|
expect(date2.getUTCHours().toString()).toBe("21");
|
|
35
33
|
expect(date2.getUTCMinutes().toString()).toBe("28");
|
|
36
34
|
expect(date2.getUTCSeconds().toString()).toBe("40");
|
|
37
|
-
expect(date2.getUTCMilliseconds().toString()).toBe("525");
|
|
35
|
+
expect(date2.getUTCMilliseconds().toString()).toBe("525");
|
|
38
36
|
});
|
|
@@ -34,9 +34,9 @@ describe("Should deserialize floats", () => {
|
|
|
34
34
|
|
|
35
35
|
expect(JSON.parse<f64>("0.000001").toString()).toBe("0.000001");
|
|
36
36
|
|
|
37
|
-
expect(JSON.parse<f64>("1e-7").toString()).toBe(1e-7.toString());
|
|
37
|
+
expect(JSON.parse<f64>("1e-7").toString()).toBe((1e-7).toString());
|
|
38
38
|
|
|
39
|
-
expect(JSON.parse<f64>("100000000000000000000.0").toString()).toBe(1e20.toString());
|
|
39
|
+
expect(JSON.parse<f64>("100000000000000000000.0").toString()).toBe((1e20).toString());
|
|
40
40
|
|
|
41
|
-
expect(JSON.parse<f64>("1e+21").toString()).toBe(1e21.toString());
|
|
41
|
+
expect(JSON.parse<f64>("1e+21").toString()).toBe((1e21).toString());
|
|
42
42
|
});
|
|
@@ -4,4 +4,4 @@ import { describe, expect } from "./lib";
|
|
|
4
4
|
describe("Should deserialize complex objects", () => {
|
|
5
5
|
const input = '{"a":{"b":{"c":[{"d":"random value 1"},{"e":["value 2","value 3"]}],"f":{"g":{"h":[1,2,3],"i":{"j":"nested value"}}}},"k":"simple value"},"l":[{"m":"another value","n":{"o":"deep nested","p":[{"q":"even deeper"},"final value"]}}],"r":null}';
|
|
6
6
|
expect(JSON.stringify(JSON.parse<Map<string, JSON.Raw>>(input))).toBe(input);
|
|
7
|
-
})
|
|
7
|
+
});
|
|
@@ -11,8 +11,8 @@ describe("Should deserialize JSON.Raw", () => {
|
|
|
11
11
|
|
|
12
12
|
describe("Should serialize Map<string, JSON.Raw>", () => {
|
|
13
13
|
const m1 = new Map<string, JSON.Raw>();
|
|
14
|
-
m1.set("hello", new JSON.Raw("
|
|
15
|
-
m1.set("pos", new JSON.Raw(
|
|
14
|
+
m1.set("hello", new JSON.Raw('"world"'));
|
|
15
|
+
m1.set("pos", new JSON.Raw('{"x":1.0,"y":2.0,"z":3.0}'));
|
|
16
16
|
|
|
17
17
|
expect(JSON.stringify(m1)).toBe('{"hello":"world","pos":{"x":1.0,"y":2.0,"z":3.0}}');
|
|
18
18
|
});
|
|
@@ -20,4 +20,4 @@ describe("Should serialize Map<string, JSON.Raw>", () => {
|
|
|
20
20
|
describe("Should deserialize Map<string, JSON.Raw>", () => {
|
|
21
21
|
const m1 = JSON.parse<Map<string, JSON.Raw>>('{"hello":"world","pos":{"x":1.0,"y":2.0,"z":3.0}}');
|
|
22
22
|
expect(JSON.stringify(m1)).toBe('{"hello":"world","pos":{"x":1.0,"y":2.0,"z":3.0}}');
|
|
23
|
-
});
|
|
23
|
+
});
|
|
@@ -53,26 +53,24 @@ describe("Should ignore properties decorated with @omit", () => {
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
describe("Should deserialize structs", () => {
|
|
56
|
-
expect(
|
|
57
|
-
JSON.stringify(JSON.parse<Vec3>('{"x":3.4,"y":1.2,"z":8.3}')),
|
|
58
|
-
).toBe('{"x":3.4,"y":1.2,"z":8.3}');
|
|
56
|
+
expect(JSON.stringify(JSON.parse<Vec3>('{"x":3.4,"y":1.2,"z":8.3}'))).toBe('{"x":3.4,"y":1.2,"z":8.3}');
|
|
59
57
|
});
|
|
60
58
|
|
|
61
59
|
describe("Should deserialize structs with whitespace", () => {
|
|
62
|
-
expect(
|
|
63
|
-
JSON.stringify(JSON.parse<Vec3>(' { "x" : 3.4 , "y" : 1.2 , "z" : 8.3 } ')),
|
|
64
|
-
).toBe('{"x":3.4,"y":1.2,"z":8.3}');
|
|
60
|
+
expect(JSON.stringify(JSON.parse<Vec3>(' { "x" : 3.4 , "y" : 1.2 , "z" : 8.3 } '))).toBe('{"x":3.4,"y":1.2,"z":8.3}');
|
|
65
61
|
});
|
|
66
62
|
|
|
67
63
|
describe("Should deserialize structs with nullable properties", () => {
|
|
68
|
-
expect(
|
|
69
|
-
JSON.stringify(JSON.parse<NullableObj>('{"bar":{"value":"test"}}'))
|
|
70
|
-
).toBe('{"bar":{"value":"test"}}');
|
|
64
|
+
expect(JSON.stringify(JSON.parse<NullableObj>('{"bar":{"value":"test"}}'))).toBe('{"bar":{"value":"test"}}');
|
|
71
65
|
|
|
72
|
-
expect(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
expect(JSON.stringify(JSON.parse<NullableObj>('{"bar":null}'))).toBe('{"bar":null}');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("Should deserialize structs with nullable arrays in properties", () => {
|
|
70
|
+
expect(JSON.stringify(JSON.parse<NullableArrayObj>('{"bars":[{"value":"test"}]}'))).toBe('{"bars":[{"value":"test"}]}');
|
|
71
|
+
|
|
72
|
+
expect(JSON.stringify(JSON.parse<NullableArrayObj>('{"bars":null}'))).toBe('{"bars":null}');
|
|
73
|
+
});
|
|
76
74
|
|
|
77
75
|
// describe("Should serialize Suite struct", () => {
|
|
78
76
|
|
|
@@ -118,33 +116,46 @@ class Player {
|
|
|
118
116
|
|
|
119
117
|
@json
|
|
120
118
|
class ObjWithStrangeKey<T> {
|
|
119
|
+
|
|
121
120
|
@alias('a\\\t"\x02b`c')
|
|
122
121
|
data!: T;
|
|
123
122
|
}
|
|
124
123
|
|
|
124
|
+
|
|
125
125
|
@json
|
|
126
126
|
class ObjectWithFloat {
|
|
127
127
|
f!: f64;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
|
|
130
131
|
@json
|
|
131
132
|
class OmitIf {
|
|
132
133
|
x: i32 = 1;
|
|
133
134
|
|
|
135
|
+
|
|
134
136
|
@omitif("this.y == -1")
|
|
135
137
|
y: i32 = -1;
|
|
136
138
|
z: i32 = 1;
|
|
137
139
|
|
|
140
|
+
|
|
138
141
|
@omitnull()
|
|
139
142
|
foo: string | null = null;
|
|
140
143
|
}
|
|
141
144
|
|
|
145
|
+
|
|
142
146
|
@json
|
|
143
147
|
class NullableObj {
|
|
144
148
|
bar: Bar | null = null;
|
|
145
149
|
}
|
|
146
150
|
|
|
151
|
+
|
|
152
|
+
@json
|
|
153
|
+
class NullableArrayObj {
|
|
154
|
+
bars: Bar[] | null = null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
147
158
|
@json
|
|
148
159
|
class Bar {
|
|
149
160
|
value: string = "";
|
|
150
|
-
}
|
|
161
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { BACK_SLASH } from "../../custom/chars";
|
|
2
2
|
import { DESERIALIZE_ESCAPE_TABLE, ESCAPE_HEX_TABLE } from "../../globals/tables";
|
|
3
3
|
|
|
4
|
-
const SPLAT_92 = i16x8.splat(92); /* \ */
|
|
5
|
-
|
|
6
4
|
/**
|
|
7
5
|
* Deserializes strings back into into their original form using SIMD operations
|
|
8
6
|
* @param src string to deserialize
|
|
@@ -11,6 +9,7 @@ const SPLAT_92 = i16x8.splat(92); /* \ */
|
|
|
11
9
|
*/
|
|
12
10
|
// todo: optimize and stuff. it works, its not pretty. ideally, i'd like this to be (nearly) branchless
|
|
13
11
|
export function deserializeString_SIMD(srcStart: usize, srcEnd: usize, dst: usize): usize {
|
|
12
|
+
const SPLAT_92 = i16x8.splat(92); /* \ */
|
|
14
13
|
let src_ptr = srcStart + 2;
|
|
15
14
|
let dst_ptr = changetype<usize>(dst);
|
|
16
15
|
|
|
@@ -10,8 +10,7 @@ export function deserializeStructArray<T extends unknown[]>(srcStart: usize, src
|
|
|
10
10
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
11
11
|
while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
|
|
12
12
|
|
|
13
|
-
if (srcStart - srcEnd == 0)
|
|
14
|
-
throw new Error("Input string had zero length or was all whitespace");
|
|
13
|
+
if (srcStart - srcEnd == 0) throw new Error("Input string had zero length or was all whitespace");
|
|
15
14
|
|
|
16
15
|
if (load<u16>(srcStart) != BRACKET_LEFT) throw new Error("Expected '[' at start of object at position " + (srcEnd - srcStart).toString());
|
|
17
16
|
if (load<u16>(srcEnd - 2) != BRACKET_RIGHT) throw new Error("Expected ']' at end of object at position " + (srcEnd - srcStart).toString());
|
|
@@ -21,7 +20,7 @@ export function deserializeStructArray<T extends unknown[]>(srcStart: usize, src
|
|
|
21
20
|
if (code == BRACE_LEFT && depth++ == 0) {
|
|
22
21
|
lastIndex = srcStart;
|
|
23
22
|
} else if (code == BRACE_RIGHT && --depth == 0) {
|
|
24
|
-
out.push(JSON.__deserialize<valueof<T>>(lastIndex, srcStart += 2));
|
|
23
|
+
out.push(JSON.__deserialize<valueof<T>>(lastIndex, (srcStart += 2)));
|
|
25
24
|
}
|
|
26
25
|
srcStart += 2;
|
|
27
26
|
}
|
|
@@ -17,8 +17,7 @@ export function deserializeMap<T extends Map<any, any>>(srcStart: usize, srcEnd:
|
|
|
17
17
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
18
18
|
while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2; // would like to optimize this later
|
|
19
19
|
|
|
20
|
-
if (srcStart - srcEnd == 0)
|
|
21
|
-
throw new Error("Input string had zero length or was all whitespace");
|
|
20
|
+
if (srcStart - srcEnd == 0) throw new Error("Input string had zero length or was all whitespace");
|
|
22
21
|
if (load<u16>(srcStart) != BRACE_LEFT) throw new Error("Expected '{' at start of object at position " + (srcEnd - srcStart).toString());
|
|
23
22
|
if (load<u16>(srcEnd - 2) != BRACE_RIGHT) throw new Error("Expected '}' at end of object at position " + (srcEnd - srcStart).toString());
|
|
24
23
|
|
|
@@ -32,7 +31,7 @@ export function deserializeMap<T extends Map<any, any>>(srcStart: usize, srcEnd:
|
|
|
32
31
|
keyEnd = srcStart;
|
|
33
32
|
// console.log("Key: " + ptrToStr(lastIndex, srcStart));
|
|
34
33
|
// console.log("Next: " + String.fromCharCode(load<u16>(srcStart + 2)));
|
|
35
|
-
while (isSpace((code = load<u16>((srcStart += 2))))) {
|
|
34
|
+
while (isSpace((code = load<u16>((srcStart += 2))))) {}
|
|
36
35
|
if (code !== COLON) throw new Error("Expected ':' after key at position " + (srcEnd - srcStart).toString());
|
|
37
36
|
isKey = false;
|
|
38
37
|
} else {
|
|
@@ -169,4 +168,4 @@ export function deserializeMap<T extends Map<any, any>>(srcStart: usize, srcEnd:
|
|
|
169
168
|
}
|
|
170
169
|
}
|
|
171
170
|
return out;
|
|
172
|
-
}
|
|
171
|
+
}
|
|
@@ -16,8 +16,7 @@ export function deserializeObject(srcStart: usize, srcEnd: usize, dst: usize): J
|
|
|
16
16
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
17
17
|
while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2; // would like to optimize this later
|
|
18
18
|
|
|
19
|
-
if (srcStart - srcEnd == 0)
|
|
20
|
-
throw new Error("Input string had zero length or was all whitespace");
|
|
19
|
+
if (srcStart - srcEnd == 0) throw new Error("Input string had zero length or was all whitespace");
|
|
21
20
|
if (load<u16>(srcStart) != BRACE_LEFT) throw new Error("Expected '{' at start of object at position " + (srcEnd - srcStart).toString());
|
|
22
21
|
if (load<u16>(srcEnd - 2) != BRACE_RIGHT) throw new Error("Expected '}' at end of object at position " + (srcEnd - srcStart).toString());
|
|
23
22
|
|
|
@@ -31,7 +30,7 @@ export function deserializeObject(srcStart: usize, srcEnd: usize, dst: usize): J
|
|
|
31
30
|
keyEnd = srcStart;
|
|
32
31
|
// console.log("Key: " + ptrToStr(lastIndex, srcStart));
|
|
33
32
|
// console.log("Next: " + String.fromCharCode(load<u16>(srcStart + 2)));
|
|
34
|
-
while (isSpace((code = load<u16>((srcStart += 2))))) {
|
|
33
|
+
while (isSpace((code = load<u16>((srcStart += 2))))) {}
|
|
35
34
|
if (code !== COLON) throw new Error("Expected ':' after key at position " + (srcEnd - srcStart).toString());
|
|
36
35
|
isKey = false;
|
|
37
36
|
} else {
|