json-as 1.0.8 → 1.1.0
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 +12 -0
- package/README.md +10 -1
- package/assembly/__benches__/lib/bench.ts +1 -1
- package/assembly/__benches__/vec3.bench.ts +0 -46
- package/assembly/__tests__/custom.spec.ts +4 -3
- package/assembly/__tests__/hierarchy.spec.ts +55 -0
- package/assembly/__tests__/lib/index.ts +1 -0
- package/assembly/deserialize/simple/array/box.ts +36 -36
- package/assembly/deserialize/simple/array.ts +1 -1
- package/assembly/index.ts +23 -30
- package/assembly/test.ts +124 -177
- package/assembly/util/idofd.ts +6 -0
- package/bench/lib/bench.ts +3 -3
- package/bench/runners/assemblyscript.js +17 -19
- package/package.json +1 -1
- package/transform/lib/index.js +383 -120
- package/transform/lib/index.js.map +1 -1
- package/transform/lib/types.js +1 -0
- package/transform/lib/types.js.map +1 -1
- package/transform/src/index.ts +431 -117
- package/transform/src/types.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 2025-05-22 - 1.1.0
|
|
4
|
+
|
|
5
|
+
- fix: change __DESERIALIZE<T> to __JSON_T to avoid populating local scope
|
|
6
|
+
|
|
7
|
+
## 2025-05-22 - 1.0.9
|
|
8
|
+
|
|
9
|
+
- fix: [#132](https://github.com/JairusSW/json-as/issues/132)
|
|
10
|
+
- feat: allow base classes to use their child classes if the signatures match
|
|
11
|
+
- perf: rewrite struct deserialization to be significantly faster
|
|
12
|
+
- fix: [#131](https://github.com/JairusSW/json-as/issues/131) Generic classes with custom deserializer crashing
|
|
13
|
+
- fix: [#66](https://github.com/JairusSW/json-as/issues/66) Throw error when additional keys are in JSON
|
|
14
|
+
|
|
3
15
|
## 2025-05-21 - 1.0.8
|
|
4
16
|
|
|
5
17
|
- fix: inline warnings on layer-2 serialize and deserialize functions
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
7
7
|
█████ ███████ ██████ ██ ████ ██ ██ ███████
|
|
8
8
|
</span>
|
|
9
|
-
AssemblyScript - v1.0
|
|
9
|
+
AssemblyScript - v1.1.0
|
|
10
10
|
</pre>
|
|
11
11
|
</h6>
|
|
12
12
|
|
|
@@ -57,6 +57,7 @@ If you'd like to see the code that the transform generates, run the build step w
|
|
|
57
57
|
```typescript
|
|
58
58
|
import { JSON } from "json-as";
|
|
59
59
|
|
|
60
|
+
|
|
60
61
|
@json
|
|
61
62
|
class Vec3 {
|
|
62
63
|
x: f32 = 0.0;
|
|
@@ -64,6 +65,7 @@ class Vec3 {
|
|
|
64
65
|
z: f32 = 0.0;
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
|
|
67
69
|
@json
|
|
68
70
|
class Player {
|
|
69
71
|
@alias("first name")
|
|
@@ -109,6 +111,7 @@ This library allows selective omission of fields during serialization using the
|
|
|
109
111
|
This decorator excludes a field from serialization entirely.
|
|
110
112
|
|
|
111
113
|
```typescript
|
|
114
|
+
|
|
112
115
|
@json
|
|
113
116
|
class Example {
|
|
114
117
|
name!: string;
|
|
@@ -128,6 +131,7 @@ console.log(JSON.stringify(obj)); // { "name": "Jairus" }
|
|
|
128
131
|
This decorator omits a field only if its value is null.
|
|
129
132
|
|
|
130
133
|
```typescript
|
|
134
|
+
|
|
131
135
|
@json
|
|
132
136
|
class Example {
|
|
133
137
|
name!: string;
|
|
@@ -147,6 +151,7 @@ console.log(JSON.stringify(obj)); // { "name": "Jairus" }
|
|
|
147
151
|
This decorator omits a field based on a custom predicate function.
|
|
148
152
|
|
|
149
153
|
```typescript
|
|
154
|
+
|
|
150
155
|
@json
|
|
151
156
|
class Example {
|
|
152
157
|
name!: string;
|
|
@@ -174,6 +179,7 @@ AssemblyScript doesn't support using nullable primitive types, so instead, json-
|
|
|
174
179
|
For example, this schema won't compile in AssemblyScript:
|
|
175
180
|
|
|
176
181
|
```typescript
|
|
182
|
+
|
|
177
183
|
@json
|
|
178
184
|
class Person {
|
|
179
185
|
name!: string;
|
|
@@ -184,6 +190,7 @@ class Person {
|
|
|
184
190
|
Instead, use `JSON.Box` to allow nullable primitives:
|
|
185
191
|
|
|
186
192
|
```typescript
|
|
193
|
+
|
|
187
194
|
@json
|
|
188
195
|
class Person {
|
|
189
196
|
name: string;
|
|
@@ -246,6 +253,7 @@ More often, objects will be completely statically typed except for one or two va
|
|
|
246
253
|
In such cases, `JSON.Value` can be used to handle fields that may hold different types at runtime.
|
|
247
254
|
|
|
248
255
|
```typescript
|
|
256
|
+
|
|
249
257
|
@json
|
|
250
258
|
class DynamicObj {
|
|
251
259
|
id: i32 = 0;
|
|
@@ -302,6 +310,7 @@ Here's an example of creating a custom data type called `Point` which serializes
|
|
|
302
310
|
```typescript
|
|
303
311
|
import { bytes } from "json-as/assembly/util";
|
|
304
312
|
|
|
313
|
+
|
|
305
314
|
@json
|
|
306
315
|
class Point {
|
|
307
316
|
x: f64 = 0.0;
|
|
@@ -7,52 +7,6 @@ class Vec3 {
|
|
|
7
7
|
public x!: i32;
|
|
8
8
|
public y!: i32;
|
|
9
9
|
public z!: i32;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@inline __SERIALIZE(ptr: usize): void {
|
|
13
|
-
bs.proposeSize(98);
|
|
14
|
-
store<u64>(bs.offset, 9570664606466171, 0); // {"x"
|
|
15
|
-
store<u16>(bs.offset, 58, 8); // :
|
|
16
|
-
bs.offset += 10;
|
|
17
|
-
JSON.__serialize<i32>(load<i32>(ptr, offsetof<this>("x")));
|
|
18
|
-
store<u64>(bs.offset, 9570668901433388, 0); // ,"y"
|
|
19
|
-
store<u16>(bs.offset, 58, 8); // :
|
|
20
|
-
bs.offset += 10;
|
|
21
|
-
JSON.__serialize<i32>(load<i32>(ptr, offsetof<this>("y")));
|
|
22
|
-
store<u64>(bs.offset, 9570673196400684, 0); // ,"z"
|
|
23
|
-
store<u16>(bs.offset, 58, 8); // :
|
|
24
|
-
bs.offset += 10;
|
|
25
|
-
JSON.__serialize<i32>(load<i32>(ptr, offsetof<this>("z")));
|
|
26
|
-
store<u16>(bs.offset, 125, 0); // }
|
|
27
|
-
bs.offset += 2;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@inline __INITIALIZE(): this {
|
|
32
|
-
return this;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@inline __DESERIALIZE(keyStart: usize, keyEnd: usize, valStart: usize, valEnd: usize, ptr: usize): void {
|
|
37
|
-
switch (load<u16>(keyStart)) {
|
|
38
|
-
case 120: {
|
|
39
|
-
// x
|
|
40
|
-
store<i32>(ptr, JSON.__deserialize<i32>(valStart, valEnd, 0), offsetof<this>("x"));
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
case 121: {
|
|
44
|
-
// y
|
|
45
|
-
store<i32>(ptr, JSON.__deserialize<i32>(valStart, valEnd, 0), offsetof<this>("y"));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
case 122: {
|
|
49
|
-
// z
|
|
50
|
-
store<i32>(ptr, JSON.__deserialize<i32>(valStart, valEnd, 0), offsetof<this>("z"));
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
10
|
}
|
|
57
11
|
|
|
58
12
|
const v1: Vec3 = { x: 1, y: 2, z: 3 };
|
|
@@ -32,11 +32,12 @@ class Point {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
|
|
35
36
|
@json
|
|
36
|
-
|
|
37
|
-
value: Point = new Point(0, 0)
|
|
37
|
+
class ObjectWithCustom {
|
|
38
|
+
value: Point = new Point(0, 0);
|
|
38
39
|
constructor(value: Point) {
|
|
39
|
-
this.value = value
|
|
40
|
+
this.value = value;
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { OBJECT, TOTAL_OVERHEAD } from "rt/common";
|
|
2
|
+
import { JSON } from "..";
|
|
3
|
+
import { describe, expect } from "./lib";
|
|
4
|
+
|
|
5
|
+
@json
|
|
6
|
+
class Foo {
|
|
7
|
+
a: i32 = 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@json
|
|
11
|
+
class Bar extends Foo {
|
|
12
|
+
b: i32 = 0;
|
|
13
|
+
|
|
14
|
+
@serializer
|
|
15
|
+
serialize(self: Bar): string {
|
|
16
|
+
return `"bar"`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@deserializer
|
|
20
|
+
deserialize(data: string): Bar {
|
|
21
|
+
return data == "\"bar\"" ? {
|
|
22
|
+
a: 1,
|
|
23
|
+
b: 2,
|
|
24
|
+
} : new Bar();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("should use custom serializer for subclasses", () => {
|
|
29
|
+
const bar = new Bar();
|
|
30
|
+
bar.a = 1;
|
|
31
|
+
bar.b = 2;
|
|
32
|
+
const data = JSON.stringify(bar);
|
|
33
|
+
expect(data).toBe('"bar"');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("should use custom serializer for subclasses when type is the parent", () => {
|
|
37
|
+
const bar = new Bar();
|
|
38
|
+
bar.a = 1;
|
|
39
|
+
bar.b = 2;
|
|
40
|
+
const data = JSON.stringify<Foo>(bar);
|
|
41
|
+
expect(data).toBe('"bar"');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("should use custom deserializer for subclass", () => {
|
|
45
|
+
const json = '"bar"';
|
|
46
|
+
const bar = JSON.parse<Bar>(json);
|
|
47
|
+
expect(bar.a.toString()).toBe("1");
|
|
48
|
+
expect(bar.b.toString()).toBe("2");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("should use custom deserializer even when type is the parent", () => {
|
|
52
|
+
const json = '"bar"';
|
|
53
|
+
const foo = JSON.parse<Bar>(json);
|
|
54
|
+
expect(foo.a.toString()).toBe("1");
|
|
55
|
+
});
|
|
@@ -3,43 +3,43 @@ import { COMMA, BRACKET_RIGHT } from "../../../custom/chars";
|
|
|
3
3
|
import { JSON } from "../../..";
|
|
4
4
|
|
|
5
5
|
export function deserializeBoxArray<T extends JSON.Box<any>[]>(srcStart: usize, srcEnd: usize, dst: usize): T {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
6
|
+
const out = changetype<nonnull<T>>(dst || changetype<usize>(instantiate<T>()));
|
|
7
|
+
if (isBoolean<valueof<T>>()) {
|
|
8
|
+
srcStart += 2; // skip [
|
|
9
|
+
while (srcStart < srcEnd) {
|
|
10
|
+
const block = load<u64>(srcStart);
|
|
11
|
+
if (block == 28429475166421108) {
|
|
12
|
+
out.push(JSON.Box.from(true));
|
|
13
|
+
srcStart += 10;
|
|
14
|
+
} else if (block == 32370086184550502 && load<u16>(srcStart, 8) == 101) {
|
|
15
|
+
out.push(JSON.Box.from(false));
|
|
16
|
+
srcStart += 12;
|
|
17
|
+
} else {
|
|
18
|
+
srcStart += 2;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
} else {
|
|
23
|
+
let lastIndex: usize = 0;
|
|
24
|
+
while (srcStart < srcEnd) {
|
|
25
|
+
const code = load<u16>(srcStart);
|
|
26
|
+
if (code - 48 <= 9 || code == 45) {
|
|
27
|
+
lastIndex = srcStart;
|
|
28
|
+
srcStart += 2;
|
|
24
29
|
while (srcStart < srcEnd) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// /* empty */
|
|
35
|
-
// }
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
srcStart += 2;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
srcStart += 2;
|
|
30
|
+
const code = load<u16>(srcStart);
|
|
31
|
+
if (code == COMMA || code == BRACKET_RIGHT || isSpace(code)) {
|
|
32
|
+
out.push(JSON.__deserialize<valueof<T>>(lastIndex, srcStart));
|
|
33
|
+
// while (isSpace(load<u16>((srcStart += 2)))) {
|
|
34
|
+
// /* empty */
|
|
35
|
+
// }
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
srcStart += 2;
|
|
42
39
|
}
|
|
40
|
+
}
|
|
41
|
+
srcStart += 2;
|
|
43
42
|
}
|
|
44
|
-
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
45
|
}
|
|
@@ -37,7 +37,7 @@ export function deserializeArray<T extends unknown[]>(srcStart: usize, srcEnd: u
|
|
|
37
37
|
} else if (type instanceof JSON.Obj) {
|
|
38
38
|
// @ts-ignore: type
|
|
39
39
|
return deserializeObjectArray<T>(srcStart, srcEnd, dst);
|
|
40
|
-
}else if (type instanceof Map) {
|
|
40
|
+
} else if (type instanceof Map) {
|
|
41
41
|
// @ts-ignore: type
|
|
42
42
|
return deserializeMapArray<T>(srcStart, srcEnd, dst);
|
|
43
43
|
// @ts-ignore: defined by transform
|
package/assembly/index.ts
CHANGED
|
@@ -28,9 +28,6 @@ import { serializeObject } from "./serialize/simple/object";
|
|
|
28
28
|
import { deserializeObject } from "./deserialize/simple/object";
|
|
29
29
|
import { serializeRaw } from "./serialize/simple/raw";
|
|
30
30
|
import { deserializeRaw } from "./deserialize/simple/raw";
|
|
31
|
-
import { isSpace } from "util/string";
|
|
32
|
-
import { deserializeString_SIMD } from "./deserialize/simd/string";
|
|
33
|
-
import { serializeString_SIMD } from "./serialize/simd/string";
|
|
34
31
|
|
|
35
32
|
/**
|
|
36
33
|
* Offset of the 'storage' property in the JSON.Value class.
|
|
@@ -119,15 +116,10 @@ export namespace JSON {
|
|
|
119
116
|
// if (ASC_FEATURE_SIMD) {
|
|
120
117
|
// serializeString_SIMD(data as string);
|
|
121
118
|
// } else {
|
|
122
|
-
|
|
119
|
+
serializeString(data as string);
|
|
123
120
|
// }
|
|
124
121
|
return bs.out<string>();
|
|
125
122
|
// @ts-ignore: Supplied by transform
|
|
126
|
-
} else if (isDefined(data.__SERIALIZE_CUSTOM)) {
|
|
127
|
-
// @ts-ignore
|
|
128
|
-
inline.always(data.__SERIALIZE_CUSTOM());
|
|
129
|
-
return bs.out<string>();
|
|
130
|
-
// @ts-ignore: Supplied by transform
|
|
131
123
|
} else if (isDefined(data.__SERIALIZE)) {
|
|
132
124
|
// @ts-ignore
|
|
133
125
|
inline.always(data.__SERIALIZE(changetype<usize>(data)));
|
|
@@ -200,19 +192,12 @@ export namespace JSON {
|
|
|
200
192
|
} else {
|
|
201
193
|
let type: nonnull<T> = changetype<nonnull<T>>(0);
|
|
202
194
|
// @ts-ignore: Defined by transform
|
|
203
|
-
if (isDefined(type.
|
|
204
|
-
const out = changetype<nonnull<T>>(
|
|
195
|
+
if (isDefined(type.__DESERIALIZE)) {
|
|
196
|
+
const out = changetype<nonnull<T>>(__new(offsetof<nonnull<T>>(), idof<nonnull<T>>()));
|
|
205
197
|
// @ts-ignore: Defined by transform
|
|
206
198
|
if (isDefined(type.__INITIALIZE)) out.__INITIALIZE();
|
|
207
199
|
// @ts-ignore
|
|
208
|
-
return out.
|
|
209
|
-
// @ts-ignore: Defined by transform
|
|
210
|
-
} else if (isDefined(type.__DESERIALIZE)) {
|
|
211
|
-
const out = __new(offsetof<nonnull<T>>(), idof<nonnull<T>>());
|
|
212
|
-
// @ts-ignore: Defined by transform
|
|
213
|
-
if (isDefined(type.__INITIALIZE)) changetype<nonnull<T>>(out).__INITIALIZE();
|
|
214
|
-
// @ts-ignore
|
|
215
|
-
return inline.always(deserializeStruct<nonnull<T>>(dataPtr, dataPtr + dataSize, out));
|
|
200
|
+
return out.__DESERIALIZE(dataPtr, dataPtr + dataSize, out);
|
|
216
201
|
} else if (type instanceof Map) {
|
|
217
202
|
// @ts-ignore
|
|
218
203
|
return inline.always(deserializeMap<nonnull<T>>(dataPtr, dataPtr + dataSize, 0));
|
|
@@ -408,7 +393,7 @@ export namespace JSON {
|
|
|
408
393
|
case JSON.Types.Array: {
|
|
409
394
|
const arr = this.get<JSON.Value[]>();
|
|
410
395
|
if (!arr.length) return "[]";
|
|
411
|
-
let out = "["
|
|
396
|
+
let out = "[";
|
|
412
397
|
const end = arr.length - 1;
|
|
413
398
|
for (let i = 0; i < end; i++) {
|
|
414
399
|
const element = unchecked(arr[i]);
|
|
@@ -440,7 +425,7 @@ export namespace JSON {
|
|
|
440
425
|
// @ts-ignore: type
|
|
441
426
|
private storage: Map<string, JSON.Value> = new Map<string, JSON.Value>();
|
|
442
427
|
|
|
443
|
-
constructor() {
|
|
428
|
+
constructor() {}
|
|
444
429
|
|
|
445
430
|
// @ts-ignore: decorator
|
|
446
431
|
@inline get size(): i32 {
|
|
@@ -602,15 +587,10 @@ export namespace JSON {
|
|
|
602
587
|
} else {
|
|
603
588
|
let type: nonnull<T> = changetype<nonnull<T>>(0);
|
|
604
589
|
// @ts-ignore: Defined by transform
|
|
605
|
-
if (isDefined(type.
|
|
606
|
-
const out = __new(offsetof<nonnull<T>>(), idof<nonnull<T>>())
|
|
607
|
-
// @ts-ignore: Defined by transform
|
|
608
|
-
if (isDefined(type.__INITIALIZE)) changetype<nonnull<T>>(out).__INITIALIZE();
|
|
609
|
-
// @ts-ignore
|
|
610
|
-
return changetype<nonnull<T>>(out).__DESERIALIZE_CUSTOM(ptrToStr(srcStart, srcEnd));
|
|
590
|
+
if (isDefined(type.__DESERIALIZE)) {
|
|
591
|
+
const out = changetype<nonnull<T>>(dst || __new(offsetof<nonnull<T>>(), idof<nonnull<T>>()))
|
|
611
592
|
// @ts-ignore: Defined by transform
|
|
612
|
-
|
|
613
|
-
return deserializeStruct<T>(srcStart, srcEnd, dst);
|
|
593
|
+
return out.__DESERIALIZE(srcStart, srcEnd, out);
|
|
614
594
|
} else if (type instanceof Map) {
|
|
615
595
|
// @ts-ignore: type
|
|
616
596
|
return deserializeMap<T>(srcStart, srcEnd, dst);
|
|
@@ -623,7 +603,7 @@ export namespace JSON {
|
|
|
623
603
|
} else if (type instanceof JSON.Value) {
|
|
624
604
|
// @ts-ignore: type
|
|
625
605
|
return deserializeArbitrary(srcStart, srcEnd, 0);
|
|
626
|
-
}else if (type instanceof JSON.Obj) {
|
|
606
|
+
} else if (type instanceof JSON.Obj) {
|
|
627
607
|
// @ts-ignore: type
|
|
628
608
|
return deserializeObject(srcStart, srcEnd, 0);
|
|
629
609
|
} else if (type instanceof JSON.Box) {
|
|
@@ -633,6 +613,19 @@ export namespace JSON {
|
|
|
633
613
|
}
|
|
634
614
|
throw new Error(`Could not deserialize data '${ptrToStr(srcStart, srcEnd).slice(0, 100)}' to type. Make sure to add the correct decorators to classes.`);
|
|
635
615
|
}
|
|
616
|
+
namespace Util {
|
|
617
|
+
// @ts-ignore: decorator
|
|
618
|
+
@inline export function isSpace(code: u16): boolean {
|
|
619
|
+
return code == 0x20 || code - 9 <= 4;
|
|
620
|
+
}
|
|
621
|
+
// @ts-ignore: decorator
|
|
622
|
+
@inline export function ptrToStr(start: usize, end: usize): string {
|
|
623
|
+
const size = end - start;
|
|
624
|
+
const out = __new(size, idof<string>());
|
|
625
|
+
memory.copy(out, start, size);
|
|
626
|
+
return changetype<string>(out);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
636
629
|
}
|
|
637
630
|
|
|
638
631
|
// @ts-ignore: decorator
|