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 +4 -0
- package/README.md +1 -1
- package/assembly/__tests__/namespace.spec.ts +63 -0
- package/assembly/test.tmp.ts +7 -0
- package/assembly/test.ts +2 -0
- package/package.json +5 -5
- package/run-tests.sh +17 -13
- package/transform/lib/index.js +32 -37
- package/transform/lib/index.js.map +1 -1
- package/transform/lib/types.js +103 -2
- package/transform/lib/types.js.map +1 -1
- package/transform/src/index.ts +36 -37
- package/transform/src/types.ts +182 -4
- package/transform/src/linkers/classes.ts +0 -50
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -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
|
+
}
|
package/assembly/test.tmp.ts
CHANGED
|
@@ -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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-as",
|
|
3
|
-
"version": "1.1.
|
|
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.
|
|
13
|
-
"assemblyscript": "^0.28.
|
|
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.
|
|
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
|
-
|
|
7
|
+
if [ -z "$1" ] || [ "$1" = "$filename" ]; then
|
|
8
|
+
output="./build/${filename%.ts}.wasm"
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
14
|
+
build_time=$((end_time - start_time))
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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"
|
package/transform/lib/index.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
|
55
|
+
schema.name = source.getQualifiedName(node);
|
|
63
56
|
if (node.extendsType) {
|
|
64
|
-
const extendsName = node
|
|
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
|
|
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(
|
|
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 ==
|
|
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,
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 " +
|
|
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 =
|
|
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
|
|
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(
|
|
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 ==
|
|
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 ==
|
|
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,
|
|
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
|
-
|
|
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(
|
|
177
|
+
const externalSchema = this.schemas.get(externalSource.internalPath)?.find((s) => s.name == unknownType);
|
|
183
178
|
schema.deps.push(externalSchema);
|
|
184
|
-
this.schemas.get(
|
|
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(
|
|
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 " +
|
|
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(
|
|
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 =
|
|
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("=>"))
|