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.
Files changed (35) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/README.md +32 -22
  3. package/SECURITY.md +2 -1
  4. package/assembly/__benches__/large.bench.ts +3 -0
  5. package/assembly/__benches__/vec3.bench.ts +3 -0
  6. package/assembly/__tests__/arbitrary.spec.ts +3 -3
  7. package/assembly/__tests__/array.spec.ts +5 -8
  8. package/assembly/__tests__/box.spec.ts +10 -20
  9. package/assembly/__tests__/custom.spec.ts +7 -6
  10. package/assembly/__tests__/date.spec.ts +4 -6
  11. package/assembly/__tests__/float.spec.ts +3 -3
  12. package/assembly/__tests__/map.spec.ts +1 -1
  13. package/assembly/__tests__/raw.spec.ts +3 -3
  14. package/assembly/__tests__/struct.spec.ts +25 -14
  15. package/assembly/deserialize/simd/string.ts +1 -2
  16. package/assembly/deserialize/simple/array/struct.ts +2 -3
  17. package/assembly/deserialize/simple/array.ts +1 -1
  18. package/assembly/deserialize/simple/bool.ts +1 -1
  19. package/assembly/deserialize/simple/map.ts +3 -4
  20. package/assembly/deserialize/simple/object.ts +2 -3
  21. package/assembly/deserialize/simple/raw.ts +1 -1
  22. package/assembly/deserialize/simple/struct.ts +2 -3
  23. package/assembly/index.ts +26 -10
  24. package/assembly/serialize/simd/string.ts +1 -0
  25. package/assembly/serialize/simple/integer.ts +1 -1
  26. package/assembly/serialize/simple/object.ts +6 -6
  27. package/assembly/test.ts +26 -24
  28. package/bench.js +2 -4
  29. package/index.ts +1 -1
  30. package/lib/as-bs.ts +4 -13
  31. package/package.json +4 -2
  32. package/run-tests.sh +1 -1
  33. package/transform/lib/index.js +61 -63
  34. package/transform/lib/index.js.map +1 -1
  35. 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
- <h5 align="center">
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.4
9
+ AssemblyScript - v1.0.6
10
10
  </pre>
11
- </h5>
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("Values: " +
243
- obj
244
- .values()
245
- .map<string>((v) => JSON.stringify(v))
246
- .join(" "),
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("\"hello world\"");
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>("\"hello world\"").get<string>()).toBe("hello world");
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('[0,100,101]');
63
- expect(JSON.stringify(JSON.parse<u64[]>("[0,100,101]"))).toBe('[0,100,101]');
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[]>("[\"string \\\"with random spa\\nces and \\nnewlines\\n\\n\\n\"]"))).toBe("[\"string \\\"with random spa\\nces and \\nnewlines\\n\\n\\n\"]");
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
- .toBe('"1970-01-01T00:00:00.000Z"');
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("\"world\""));
15
- m1.set("pos", new JSON.Raw("{\"x\":1.0,\"y\":2.0,\"z\":3.0}"));
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
- JSON.stringify(JSON.parse<NullableObj>('{"bar":null}'))
74
- ).toBe('{"bar":null}');
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
  }
@@ -43,4 +43,4 @@ export function deserializeArray<T extends unknown[]>(srcStart: usize, srcEnd: u
43
43
  } else {
44
44
  throw new Error("Could not parse array of type " + nameof<T>() + "!");
45
45
  }
46
- }
46
+ }
@@ -4,4 +4,4 @@
4
4
  if (block == 28429475166421108) return true;
5
5
  else if (block == 32370086184550502 && load<u16>(srcStart, 8) == 101) return false;
6
6
  return false; //throw new Error(`Expected to find boolean, but found "${data.slice(0, 100)}" instead!`);
7
- }
7
+ }
@@ -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 {