json-as 1.0.4 → 1.0.5

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 +26 -8
  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 +5 -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 +24 -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 +23 -7
  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 +7 -6
  27. package/assembly/test.ts +23 -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 +5 -3
  32. package/run-tests.sh +1 -1
  33. package/transform/lib/index.js +58 -55
  34. package/transform/lib/index.js.map +1 -1
  35. package/transform/src/index.ts +72 -131
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
@@ -6,7 +6,7 @@
6
6
  ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
7
7
  █████ ███████ ██████ ██ ████ ██ ██ ███████
8
8
  </span>
9
- AssemblyScript - v1.0.4
9
+ AssemblyScript - v1.0.5
10
10
  </pre>
11
11
  </h5>
12
12
 
@@ -69,6 +69,7 @@ If you'd like to see the code that the transform generates, run the build step w
69
69
  ```typescript
70
70
  import { JSON } from "json-as";
71
71
 
72
+
72
73
  @json
73
74
  class Vec3 {
74
75
  x: f32 = 0.0;
@@ -76,8 +77,10 @@ class Vec3 {
76
77
  z: f32 = 0.0;
77
78
  }
78
79
 
80
+
79
81
  @json
80
82
  class Player {
83
+
81
84
  @alias("first name")
82
85
  firstName!: string;
83
86
  lastName!: string;
@@ -85,6 +88,7 @@ class Player {
85
88
  // Drop in a code block, function, or expression that evaluates to a boolean
86
89
  @omitif((self: Player) => self.age < 18)
87
90
  age!: i32;
91
+
88
92
  @omitnull()
89
93
  pos!: Vec3 | null;
90
94
  isVerified!: boolean;
@@ -101,7 +105,7 @@ const player: Player = {
101
105
  z: 8.3,
102
106
  },
103
107
  isVerified: true,
104
- }
108
+ };
105
109
 
106
110
  const serialized = JSON.stringify<Player>(player);
107
111
  const deserialized = JSON.parse<Player>(serialized);
@@ -121,9 +125,11 @@ This library allows selective omission of fields during serialization using the
121
125
  This decorator excludes a field from serialization entirely.
122
126
 
123
127
  ```typescript
128
+
124
129
  @json
125
130
  class Example {
126
131
  name!: string;
132
+
127
133
  @omit
128
134
  SSN!: string;
129
135
  }
@@ -140,9 +146,11 @@ console.log(JSON.stringify(obj)); // { "name": "Jairus" }
140
146
  This decorator omits a field only if its value is null.
141
147
 
142
148
  ```typescript
149
+
143
150
  @json
144
151
  class Example {
145
152
  name!: string;
153
+
146
154
  @omitnull()
147
155
  optionalField!: string | null;
148
156
  }
@@ -159,9 +167,11 @@ console.log(JSON.stringify(obj)); // { "name": "Jairus" }
159
167
  This decorator omits a field based on a custom predicate function.
160
168
 
161
169
  ```typescript
170
+
162
171
  @json
163
172
  class Example {
164
173
  name!: string;
174
+
165
175
  @omitif((self: Example) => self.age <= 18)
166
176
  age!: number;
167
177
  }
@@ -186,6 +196,7 @@ AssemblyScript doesn't support using nullable primitive types, so instead, json-
186
196
  For example, this schema won't compile in AssemblyScript:
187
197
 
188
198
  ```typescript
199
+
189
200
  @json
190
201
  class Person {
191
202
  name!: string;
@@ -196,6 +207,7 @@ class Person {
196
207
  Instead, use `JSON.Box` to allow nullable primitives:
197
208
 
198
209
  ```typescript
210
+
199
211
  @json
200
212
  class Person {
201
213
  name: string;
@@ -239,11 +251,12 @@ When dealing with an object with an unknown structure, use the `JSON.Obj` type
239
251
  const obj = JSON.parse<JSON.Obj>('{"a":3.14,"b":true,"c":[1,2,3],"d":{"x":1,"y":2,"z":3}}');
240
252
 
241
253
  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(" "),
254
+ console.log(
255
+ "Values: " +
256
+ obj
257
+ .values()
258
+ .map<string>((v) => JSON.stringify(v))
259
+ .join(" "),
247
260
  ); // 3.14 true [1,2,3] {"x":1,"y":2,"z":3}
248
261
 
249
262
  const y = obj.get("d")!.get<JSON.Obj>().get("y")!;
@@ -257,6 +270,7 @@ More often, objects will be completely statically typed except for one or two va
257
270
  In such cases, `JSON.Value` can be used to handle fields that may hold different types at runtime.
258
271
 
259
272
  ```typescript
273
+
260
274
  @json
261
275
  class DynamicObj {
262
276
  id: i32 = 0;
@@ -313,6 +327,7 @@ Here's an example of creating a custom data type called `Point` which serializes
313
327
  ```typescript
314
328
  import { bytes } from "json-as/assembly/util";
315
329
 
330
+
316
331
  @json
317
332
  class Point {
318
333
  x: f64 = 0.0;
@@ -322,11 +337,13 @@ class Point {
322
337
  this.y = y;
323
338
  }
324
339
 
340
+
325
341
  @serializer
326
342
  serializer(self: Point): string {
327
343
  return `(${self.x},${self.y})`;
328
344
  }
329
345
 
346
+
330
347
  @deserializer
331
348
  deserializer(data: string): Point {
332
349
  const dataSize = bytes(data);
@@ -403,6 +420,7 @@ These benchmarks compare this library to JavaScript's native `JSON.stringify` an
403
420
  - JSON-AS consistently outperforms JavaScript's native implementation.
404
421
 
405
422
  - **Serialization Speed:**
423
+
406
424
  - JSON-AS achieves speeds up to `2,133 MB/s`, significantly faster than JavaScript's peak of `1,416 MB/s`.
407
425
  - Large objects see the biggest improvement, with JSON-AS at `2,074 MB/s` vs. JavaScript’s `749.7 MB/s`.
408
426
 
@@ -457,4 +475,4 @@ Please send all issues to [GitHub Issues](https://github.com/JairusSW/json-as/is
457
475
  - **Email:** Send me inquiries, questions, or requests at [me@jairus.dev](mailto:me@jairus.dev)
458
476
  - **GitHub:** Visit the official GitHub repository [Here](https://github.com/JairusSW/json-as)
459
477
  - **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/)
478
+ - **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,12 @@ class Point {
10
11
  this.x = x;
11
12
  this.y = y;
12
13
  }
14
+
13
15
  @serializer
14
16
  serializer(self: Point): string {
15
17
  return `(${self.x},${self.y})`;
16
18
  }
19
+
17
20
  @deserializer
18
21
  deserializer(data: string): Point {
19
22
  const dataSize = bytes(data);
@@ -23,16 +26,12 @@ class Point {
23
26
  const x = data.slice(1, c);
24
27
  const y = data.slice(c + 1, data.length - 1);
25
28
 
26
- return new Point(
27
- f64.parse(x),
28
- f64.parse(y)
29
- );
29
+ return new Point(f64.parse(x), f64.parse(y));
30
30
  }
31
31
  }
32
32
 
33
-
34
33
  describe("Should serialize using custom serializers", () => {
35
- expect(JSON.stringify<Point>(new Point(1,2))).toBe("(1.0,2.0)");
34
+ expect(JSON.stringify<Point>(new Point(1, 2))).toBe("(1.0,2.0)");
36
35
  });
37
36
 
38
37
  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,45 @@ 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
+ @json
152
+ class NullableArrayObj {
153
+ bars: Bar[] | null = null;
154
+ }
155
+
156
+
147
157
  @json
148
158
  class Bar {
149
159
  value: string = "";
150
- }
160
+ }
@@ -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 {
@@ -4,4 +4,4 @@ import { ptrToStr } from "../../util/ptrToStr";
4
4
  // @ts-ignore: inline
5
5
  @inline export function deserializeRaw(srcStart: usize, srcEnd: usize): JSON.Raw {
6
6
  return JSON.Raw.from(ptrToStr(srcStart, srcEnd));
7
- }
7
+ }
@@ -16,8 +16,7 @@ export function deserializeStruct<T>(srcStart: usize, srcEnd: usize, dst: usize)
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 deserializeStruct<T>(srcStart: usize, srcEnd: usize, dst: usize)
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 {