json-as 1.1.16 → 1.1.17

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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Change Log
2
2
 
3
+ ## 2025-06-17 - 1.1.17
4
+
5
+ - fix: add support for classes within namespaces [#147](https://github.com/JairusSW/json-as/pull/147)
6
+
3
7
  ## 2025-06-12 - 1.1.16
4
8
 
5
9
  - tests: properly support nulls (in testing lib)
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
7
7
  █████ ███████ ██████ ██ ████ ██ ██ ███████
8
8
  </span>
9
- AssemblyScript - v1.1.16
9
+ AssemblyScript - v1.1.17
10
10
  </pre>
11
11
  </h6>
12
12
 
@@ -0,0 +1,63 @@
1
+ import { JSON } from "..";
2
+ import { describe, expect } from "./lib";
3
+
4
+ describe("Should serialize namespaced derived structs", () => {
5
+ const obj: Namespace.DerivedObject = { a: "foo", b: "bar" };
6
+ expect(JSON.stringify(obj)).toBe(`{"a":"foo","b":"bar"}`);
7
+ });
8
+
9
+ describe("Should serialize namespaced derived structs with nested object", () => {
10
+ const bar: Namespace.Bar = { value: "baz" };
11
+ const obj: Namespace.DerivedObjectWithNestedObject = { a: "foo", b: "bar", c: bar };
12
+ expect(JSON.stringify(obj)).toBe(`{"a":"foo","b":"bar","c":{"value":"baz"}}`);
13
+ });
14
+
15
+ describe("Should deserialize namespaced object with alias property", () => {
16
+ expect(JSON.stringify(JSON.parse<Namespace.ObjectWithAliasProperty>(`{"a":"foo","value":42}`))).toBe(`{"a":"foo","value":42}`);
17
+ });
18
+
19
+ describe("Should deserialize namespaced derived structs", () => {
20
+ expect(JSON.stringify(JSON.parse<Namespace.DerivedObject>(`{"a":"foo","b":"bar"}`))).toBe(`{"a":"foo","b":"bar"}`);
21
+ expect(JSON.stringify(JSON.parse<Namespace.DerivedObject>(`{"b":"bar","a":"foo"}`))).toBe(`{"a":"foo","b":"bar"}`);
22
+ });
23
+
24
+ describe("Should deserialize namespaced derived structs with nested object", () => {
25
+ expect(JSON.stringify(JSON.parse<Namespace.DerivedObjectWithNestedObject>(`{"a":"foo","b":"bar","c":{"value":"baz"}}`))).toBe(`{"a":"foo","b":"bar","c":{"value":"baz"}}`);
26
+ expect(JSON.stringify(JSON.parse<Namespace.DerivedObjectWithNestedObject>(`{"c":{"value":"baz"},"a":"foo","b":"bar"}`))).toBe(`{"a":"foo","b":"bar","c":{"value":"baz"}}`);
27
+ });
28
+
29
+ type NumberAlias = i64;
30
+
31
+ namespace Namespace {
32
+
33
+ @json
34
+ export class Base {
35
+ a: string = "";
36
+ }
37
+
38
+
39
+ @json
40
+ export class Bar {
41
+ value: string = "";
42
+ }
43
+
44
+
45
+ @json
46
+ export class ObjectWithAliasProperty {
47
+ a: string = "";
48
+ value: NumberAlias = 0;
49
+ }
50
+
51
+
52
+ @json
53
+ export class DerivedObject extends Base {
54
+ b: string = "";
55
+ }
56
+
57
+
58
+ @json
59
+ export class DerivedObjectWithNestedObject extends Base {
60
+ b: string = "";
61
+ c: Bar = new Bar();
62
+ }
63
+ }
@@ -1,6 +1,7 @@
1
1
  import { bs } from "../lib/as-bs";
2
2
  import { JSON } from ".";
3
3
 
4
+
4
5
  @json
5
6
  class Vec3 {
6
7
  x: f32 = 0;
@@ -24,6 +25,7 @@ class Vec3 {
24
25
  bs.offset += 2;
25
26
  }
26
27
 
28
+
27
29
  @inline
28
30
  __INITIALIZE(): this {
29
31
  return this;
@@ -204,6 +206,7 @@ class Vec3 {
204
206
  }
205
207
  }
206
208
 
209
+
207
210
  @json
208
211
  class Player {
209
212
 
@@ -212,9 +215,11 @@ class Player {
212
215
  lastName!: string;
213
216
  lastActive!: Array<i32>;
214
217
 
218
+
215
219
  @omitif((self: this): boolean => self.age < 18)
216
220
  age!: i32;
217
221
 
222
+
218
223
  @omitnull()
219
224
  pos!: Vec3 | null;
220
225
  isVerified!: boolean;
@@ -266,6 +271,7 @@ class Player {
266
271
  bs.offset += 2;
267
272
  }
268
273
 
274
+
269
275
  @inline
270
276
  __INITIALIZE(): this {
271
277
  store<string>(changetype<usize>(this), "", offsetof<this>("lastName"));
@@ -598,6 +604,7 @@ class Player {
598
604
  }
599
605
  }
600
606
  const player: Player = {
607
+ firstName: "Jairus",
601
608
  lastName: "Tanaka",
602
609
  lastActive: [3, 9, 2025],
603
610
  age: 18,
package/assembly/test.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { JSON } from ".";
2
2
 
3
+
3
4
  @json
4
5
  class Vec3 {
5
6
  x: f32 = 0.0;
@@ -19,6 +20,7 @@ class Player {
19
20
  @omitif((self: Player) => self.age < 18)
20
21
  age!: i32;
21
22
 
23
+
22
24
  @omitnull()
23
25
  pos!: Vec3 | null;
24
26
  isVerified!: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-as",
3
- "version": "1.1.16",
3
+ "version": "1.1.17",
4
4
  "author": "Jairus Tanaka",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,11 +9,11 @@
9
9
  "main": "transform/lib/index.js",
10
10
  "devDependencies": {
11
11
  "@assemblyscript/wasi-shim": "^0.1.0",
12
- "@types/node": "^22.15.24",
13
- "assemblyscript": "^0.28.1",
12
+ "@types/node": "^22.15.32",
13
+ "assemblyscript": "^0.28.2",
14
14
  "assemblyscript-prettier": "^3.0.1",
15
15
  "prettier": "^3.5.3",
16
- "tsx": "^4.19.4",
16
+ "tsx": "^4.20.3",
17
17
  "typescript": "^5.8.3"
18
18
  },
19
19
  "bugs": {
@@ -57,7 +57,7 @@
57
57
  "test": "bash ./run-tests.sh",
58
58
  "bench:as": "bash ./run-bench.as.sh",
59
59
  "bench:js": "bash ./run-bench.js.sh",
60
- "build:test": "rm -rf ./build/ && JSON_DEBUG=1 JSON_WRITE=assembly/test.ts asc assembly/test.ts --transform ./transform -o ./build/test.wasm --textFile ./build/test.wat --debug --config ./node_modules/@assemblyscript/wasi-shim/asconfig.json",
60
+ "build:test": "rm -rf ./build/ && set JSON_DEBUG=1 && set JSON_WRITE=assembly/test.ts && asc assembly/test.ts --transform ./transform -o ./build/test.wasm --textFile ./build/test.wat --debug --config ./node_modules/@assemblyscript/wasi-shim/asconfig.json",
61
61
  "build:test:wine": "JSON_DEBUG=1 JSON_WRITE=assembly/test.ts NODE_SKIP_PLATFORM_CHECK=1 wine ~/.win-bin/node/node.exe ./node_modules/assemblyscript/bin/asc.js assembly/test.ts --transform ./transform -o ./build/test.wasm --textFile ./build/test.wat --debug --config ./node_modules/@assemblyscript/wasi-shim/asconfig.json",
62
62
  "test:wasmtime": "wasmtime ./build/test.wasm",
63
63
  "test:wasmer": "wasmer ./build/test.wasm",
package/run-tests.sh CHANGED
@@ -4,24 +4,28 @@ mkdir -p ./build
4
4
 
5
5
  for file in ./assembly/__tests__/*.spec.ts; do
6
6
  filename=$(basename -- "$file")
7
- output="./build/${filename%.ts}.wasm"
7
+ if [ -z "$1" ] || [ "$1" = "$filename" ]; then
8
+ output="./build/${filename%.ts}.wasm"
8
9
 
9
- start_time=$(date +%s%3N)
10
- npx asc "$file" --transform ./transform -o "$output" --enable simd --config ./node_modules/@assemblyscript/wasi-shim/asconfig.json --disableWarning 226 || { echo "Tests failed"; exit 1; }
11
- end_time=$(date +%s%3N)
10
+ start_time=$(date +%s%3N)
11
+ npx asc "$file" --transform ./transform -o "$output" --enable simd --config ./node_modules/@assemblyscript/wasi-shim/asconfig.json --disableWarning 226 || { echo "Tests failed"; exit 1; }
12
+ end_time=$(date +%s%3N)
12
13
 
13
- build_time=$((end_time - start_time))
14
+ build_time=$((end_time - start_time))
14
15
 
15
- if [ "$build_time" -ge 60000 ]; then
16
- formatted_time="$(bc <<< "scale=2; $build_time/60000")m"
17
- elif [ "$build_time" -ge 1000 ]; then
18
- formatted_time="$(bc <<< "scale=2; $build_time/1000")s"
16
+ if [ "$build_time" -ge 60000 ]; then
17
+ formatted_time="$(bc <<< "scale=2; $build_time/60000")m"
18
+ elif [ "$build_time" -ge 1000 ]; then
19
+ formatted_time="$(bc <<< "scale=2; $build_time/1000")s"
20
+ else
21
+ formatted_time="${build_time}ms"
22
+ fi
23
+
24
+ echo " -> $filename (built in $formatted_time)"
25
+ wasmtime "$output" || { echo "Tests failed"; exit 1; }
19
26
  else
20
- formatted_time="${build_time}ms"
27
+ echo " -> $filename (skipped)"
21
28
  fi
22
-
23
- echo " -> $filename (built in $formatted_time)"
24
- wasmtime "$output" || { echo "Tests failed"; exit 1; }
25
29
  done
26
30
 
27
31
  echo "All tests passed"
@@ -4,14 +4,13 @@ import { Visitor } from "./visitor.js";
4
4
  import { isStdlib, removeExtension, SimpleParser, toString } from "./util.js";
5
5
  import * as path from "path";
6
6
  import { fileURLToPath } from "url";
7
- import { Property, PropertyFlags, Schema, Src } from "./types.js";
8
- import { getClass, getImportedClass } from "./linkers/classes.js";
7
+ import { Property, PropertyFlags, Schema, SourceSet } from "./types.js";
9
8
  import { writeFileSync } from "fs";
10
9
  import { CustomTransform } from "./linkers/custom.js";
11
10
  let indent = " ";
12
11
  let id = 0;
13
- const WRITE = process.env["JSON_WRITE"];
14
- const rawValue = process.env["JSON_DEBUG"];
12
+ const WRITE = process.env["JSON_WRITE"]?.trim();
13
+ const rawValue = process.env["JSON_DEBUG"]?.trim();
15
14
  const DEBUG = rawValue === "true" ? 1 : rawValue === "false" || rawValue === "" ? 0 : isNaN(Number(rawValue)) ? 0 : Number(rawValue);
16
15
  const STRICT = process.env["JSON_STRICT"] && process.env["JSON_STRICT"] == "true";
17
16
  export class JSONTransform extends Visitor {
@@ -21,8 +20,7 @@ export class JSONTransform extends Visitor {
21
20
  parser;
22
21
  schemas = new Map();
23
22
  schema;
24
- src;
25
- sources = new Map();
23
+ sources = new SourceSet();
26
24
  imports = [];
27
25
  simdStatements = [];
28
26
  visitedClasses = new Set();
@@ -43,14 +41,9 @@ export class JSONTransform extends Visitor {
43
41
  return name === "json" || name === "serializable";
44
42
  }))
45
43
  return;
46
- const source = node.range.source;
47
- if (!this.sources.has(source.internalPath)) {
48
- this.src = new Src(source);
49
- this.sources.set(source.internalPath, this.src);
50
- }
51
- else
52
- this.src = this.sources.get(source.internalPath);
53
- if (this.visitedClasses.has(source.internalPath + node.name.text))
44
+ const source = this.sources.get(node.range.source);
45
+ const fullClassPath = source.getFullPath(node);
46
+ if (this.visitedClasses.has(fullClassPath))
54
47
  return;
55
48
  if (!this.schemas.has(source.internalPath))
56
49
  this.schemas.set(source.internalPath, []);
@@ -59,9 +52,9 @@ export class JSONTransform extends Visitor {
59
52
  const deserializers = [...node.members.filter((v) => v.kind === 58 && v.decorators && v.decorators.some((e) => e.name.text.toLowerCase() === "deserializer"))];
60
53
  const schema = new Schema();
61
54
  schema.node = node;
62
- schema.name = node.name.text;
55
+ schema.name = source.getQualifiedName(node);
63
56
  if (node.extendsType) {
64
- const extendsName = node.extendsType?.name.identifier.text;
57
+ const extendsName = source.resolveExtendsName(node);
65
58
  if (!schema.parent) {
66
59
  const depSearch = schema.deps.find((v) => v.name == extendsName);
67
60
  if (depSearch) {
@@ -72,36 +65,37 @@ export class JSONTransform extends Visitor {
72
65
  schema.parent = depSearch;
73
66
  }
74
67
  else {
75
- const internalSearch = getClass(extendsName, source);
68
+ const internalSearch = source.getClass(extendsName);
76
69
  if (internalSearch) {
77
70
  if (DEBUG > 0)
78
71
  console.log("Found " + extendsName + " internally from " + source.internalPath);
79
- if (!this.visitedClasses.has(internalSearch.range.source.internalPath + internalSearch.name.text)) {
72
+ if (!this.visitedClasses.has(source.getFullPath(internalSearch))) {
80
73
  this.visitClassDeclarationRef(internalSearch);
81
74
  this.schemas.get(internalSearch.range.source.internalPath).push(this.schema);
82
75
  this.visitClassDeclaration(node);
83
76
  return;
84
77
  }
85
- const schem = this.schemas.get(internalSearch.range.source.internalPath)?.find((s) => s.name == internalSearch.name.text);
78
+ const schem = this.schemas.get(internalSearch.range.source.internalPath)?.find((s) => s.name == extendsName);
86
79
  if (!schem)
87
80
  throw new Error("Could not find schema for " + internalSearch.name.text + " in " + internalSearch.range.source.internalPath);
88
81
  schema.deps.push(schem);
89
82
  schema.parent = schem;
90
83
  }
91
84
  else {
92
- const externalSearch = getImportedClass(extendsName, source, this.parser);
85
+ const externalSearch = source.getImportedClass(extendsName, this.parser);
93
86
  if (externalSearch) {
94
87
  if (DEBUG > 0)
95
88
  console.log("Found " + externalSearch.name.text + " externally from " + source.internalPath);
96
- if (!this.visitedClasses.has(externalSearch.range.source.internalPath + externalSearch.name.text)) {
89
+ const externalSource = this.sources.get(externalSearch.range.source);
90
+ if (!this.visitedClasses.has(externalSource.getFullPath(externalSearch))) {
97
91
  this.visitClassDeclarationRef(externalSearch);
98
- this.schemas.get(externalSearch.range.source.internalPath).push(this.schema);
92
+ this.schemas.get(externalSource.internalPath).push(this.schema);
99
93
  this.visitClassDeclaration(node);
100
94
  return;
101
95
  }
102
- const schem = this.schemas.get(externalSearch.range.source.internalPath)?.find((s) => s.name == externalSearch.name.text);
96
+ const schem = this.schemas.get(externalSource.internalPath)?.find((s) => s.name == extendsName);
103
97
  if (!schem)
104
- throw new Error("Could not find schema for " + externalSearch.name.text + " in " + externalSearch.range.source.internalPath);
98
+ throw new Error("Could not find schema for " + externalSearch.name.text + " in " + externalSource.internalPath);
105
99
  schema.deps.push(schem);
106
100
  schema.parent = schem;
107
101
  }
@@ -119,7 +113,7 @@ export class JSONTransform extends Visitor {
119
113
  }
120
114
  const getUnknownTypes = (type, types = []) => {
121
115
  type = stripNull(type);
122
- type = this.src.aliases.find((v) => stripNull(v.name) == type)?.getBaseType() || type;
116
+ type = source.aliases.find((v) => stripNull(v.name) == type)?.getBaseType() || type;
123
117
  if (type.startsWith("Array<")) {
124
118
  return getUnknownTypes(type.slice(6, -1));
125
119
  }
@@ -155,39 +149,40 @@ export class JSONTransform extends Visitor {
155
149
  }
156
150
  }
157
151
  else {
158
- const internalSearch = getClass(unknownType, source);
152
+ const internalSearch = source.getClass(unknownType);
159
153
  if (internalSearch) {
160
154
  if (DEBUG > 0)
161
155
  console.log("Found " + unknownType + " internally from " + source.internalPath);
162
- if (!this.visitedClasses.has(internalSearch.range.source.internalPath + internalSearch.name.text)) {
156
+ if (!this.visitedClasses.has(source.getFullPath(internalSearch))) {
163
157
  this.visitClassDeclarationRef(internalSearch);
164
- const internalSchema = this.schemas.get(internalSearch.range.source.internalPath)?.find((s) => s.name == internalSearch.name.text);
158
+ const internalSchema = this.schemas.get(internalSearch.range.source.internalPath)?.find((s) => s.name == unknownType);
165
159
  schema.deps.push(internalSchema);
166
160
  this.schemas.get(internalSearch.range.source.internalPath).push(this.schema);
167
161
  this.visitClassDeclaration(node);
168
162
  return;
169
163
  }
170
- const schem = this.schemas.get(internalSearch.range.source.internalPath)?.find((s) => s.name == internalSearch.name.text);
164
+ const schem = this.schemas.get(internalSearch.range.source.internalPath)?.find((s) => s.name == unknownType);
171
165
  if (!schem)
172
166
  throw new Error("Could not find schema for " + internalSearch.name.text + " in " + internalSearch.range.source.internalPath);
173
167
  schema.deps.push(schem);
174
168
  }
175
169
  else {
176
- const externalSearch = getImportedClass(unknownType, source, this.parser);
170
+ const externalSearch = source.getImportedClass(unknownType, this.parser);
177
171
  if (externalSearch) {
178
172
  if (DEBUG > 0)
179
173
  console.log("Found " + externalSearch.name.text + " externally from " + source.internalPath);
180
- if (!this.visitedClasses.has(externalSearch.range.source.internalPath + externalSearch.name.text)) {
174
+ const externalSource = this.sources.get(externalSearch.range.source);
175
+ if (!this.visitedClasses.has(externalSource.getFullPath(externalSearch))) {
181
176
  this.visitClassDeclarationRef(externalSearch);
182
- const externalSchema = this.schemas.get(externalSearch.range.source.internalPath)?.find((s) => s.name == externalSearch.name.text);
177
+ const externalSchema = this.schemas.get(externalSource.internalPath)?.find((s) => s.name == unknownType);
183
178
  schema.deps.push(externalSchema);
184
- this.schemas.get(externalSearch.range.source.internalPath).push(this.schema);
179
+ this.schemas.get(externalSource.internalPath).push(this.schema);
185
180
  this.visitClassDeclaration(node);
186
181
  return;
187
182
  }
188
- const schem = this.schemas.get(externalSearch.range.source.internalPath)?.find((s) => s.name == externalSearch.name.text);
183
+ const schem = this.schemas.get(externalSource.internalPath)?.find((s) => s.name == unknownType);
189
184
  if (!schem)
190
- throw new Error("Could not find schema for " + externalSearch.name.text + " in " + externalSearch.range.source.internalPath);
185
+ throw new Error("Could not find schema for " + externalSearch.name.text + " in " + externalSource.internalPath);
191
186
  schema.deps.push(schem);
192
187
  }
193
188
  }
@@ -196,7 +191,7 @@ export class JSONTransform extends Visitor {
196
191
  }
197
192
  this.schemas.get(source.internalPath).push(schema);
198
193
  this.schema = schema;
199
- this.visitedClasses.add(source.internalPath + node.name.text);
194
+ this.visitedClasses.add(fullClassPath);
200
195
  let SERIALIZE = "__SERIALIZE(ptr: usize): void {\n";
201
196
  let INITIALIZE = "@inline __INITIALIZE(): this {\n";
202
197
  let DESERIALIZE = "__DESERIALIZE<__JSON_T>(srcStart: usize, srcEnd: usize, out: __JSON_T): __JSON_T {\n";
@@ -257,7 +252,7 @@ export class JSONTransform extends Visitor {
257
252
  if (!member.type)
258
253
  throwError("Fields must be strongly typed", node.range);
259
254
  let type = toString(member.type);
260
- type = this.src.aliases.find((v) => stripNull(v.name) == stripNull(type))?.getBaseType() || type;
255
+ type = source.aliases.find((v) => stripNull(v.name) == stripNull(type))?.getBaseType() || type;
261
256
  const name = member.name;
262
257
  const value = member.initializer ? toString(member.initializer) : null;
263
258
  if (type.startsWith("(") && type.includes("=>"))