json-as 1.1.9 → 1.1.11

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,21 @@
1
1
  # Change Log
2
2
 
3
+ ## 2025-05-28 - 1.1.11
4
+
5
+ - fix: class resolving should only search top level statements for class declarations
6
+ - fix: add helpful error if class is missing an @json decorator
7
+ - fix: properly calculate relative path when json-as is a library
8
+ - fix: add proper null check when resolving imported classes
9
+
10
+ ## 2025-05-28 - 1.1.10
11
+
12
+ - feat: add more debug levels (1 = print transform code, 2 = print keys/values at runtime)
13
+ - feat: add write out feature (`JSON_WRITE=path-to-file.ts`) which writes out generated code
14
+ - fix: complete full parity between port and original version for correct deserialization of all types
15
+ - feat: add proper schema resolution and dependency resolution
16
+ - feat: add proper type resolution to schema fields
17
+ - fix: properly calculate the relative path between imports to modules
18
+
3
19
  ## 2025-05-27 - 1.1.9
4
20
 
5
21
  - change: strict mode is disabled by default. Enable it with JSON_STRICT=true
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
7
7
  █████ ███████ ██████ ██ ████ ██ ██ ███████
8
8
  </span>
9
- AssemblyScript - v1.1.9
9
+ AssemblyScript - v1.1.11
10
10
  </pre>
11
11
  </h6>
12
12
 
@@ -25,6 +25,7 @@ JSON is the de-facto serialization format of modern web applications, but its se
25
25
  - [Using Raw JSON Strings](#️-using-raw-json-strings)
26
26
  - [Custom Serializers](#️-using-custom-serializers-or-deserializers)
27
27
  - [Performance](#-performance)
28
+ - [Debugging](#-debugging)
28
29
  - [License](#-license)
29
30
  - [Contact](#-contact)
30
31
 
@@ -445,6 +446,12 @@ These benchmarks compare this library to JavaScript's native `JSON.stringify` an
445
446
  - Inline specific hot code paths
446
447
  - Implement error handling implementation
447
448
 
449
+ ## 🐛 Debugging
450
+
451
+ `JSON_DEBUG=1` - Prints out generated code at compile-time
452
+ `JSON_DEBUG=2` - The above and prints keys/values as they are deserialized
453
+ `JSON_WRITE=path-to-file.ts` - Writes out generated code to `path-to-file.json.ts` for easy inspection
454
+
448
455
  ## 📃 License
449
456
 
450
457
  This project is distributed under an open source license. You can view the full license using the following link: [License](./LICENSE)
@@ -2,11 +2,10 @@ import { JSON } from "..";
2
2
  import { expect } from "../__tests__/lib";
3
3
  import { bench } from "./lib/bench";
4
4
 
5
-
6
5
  @json
7
6
  class RepoOwner {
8
7
  public login!: string;
9
- public id!: i32;
8
+ public id!: number;
10
9
  public node_id!: string;
11
10
  public avatar_url!: string;
12
11
  public gravatar_id!: string;
@@ -26,7 +25,6 @@ class RepoOwner {
26
25
  public site_admin!: boolean;
27
26
  }
28
27
 
29
-
30
28
  @json
31
29
  class RepoLicense {
32
30
  public key!: string;
@@ -36,10 +34,9 @@ class RepoLicense {
36
34
  public node_id!: string;
37
35
  }
38
36
 
39
-
40
37
  @json
41
38
  class Repo {
42
- public id!: i32;
39
+ public id!: number;
43
40
  public node_id!: string;
44
41
  public name!: string;
45
42
  public full_name!: string;
@@ -93,9 +90,9 @@ class Repo {
93
90
  public clone_url!: string;
94
91
  public svn_url!: string;
95
92
  public homepage!: string | null;
96
- public size!: i32;
97
- public stargazers_count!: i32;
98
- public watchers_count!: i32;
93
+ public size!: number;
94
+ public stargazers_count!: number;
95
+ public watchers_count!: number;
99
96
  public language!: string | null;
100
97
  public has_issues!: boolean;
101
98
  public has_projects!: boolean;
@@ -103,24 +100,24 @@ class Repo {
103
100
  public has_wiki!: boolean;
104
101
  public has_pages!: boolean;
105
102
  public has_discussions!: boolean;
106
- public forks_count!: i32;
103
+ public forks_count!: number;
107
104
  public mirror_url!: string | null;
108
105
  public archived!: boolean;
109
106
  public disabled!: boolean;
110
- public open_issues_count!: i32;
107
+ public open_issues_count!: number;
111
108
  public license!: RepoLicense | null;
112
109
  public allow_forking!: boolean;
113
110
  public is_template!: boolean;
114
111
  public web_commit_signoff_required!: boolean;
115
112
  public topics!: string[];
116
113
  public visibility!: string;
117
- public forks!: i32;
118
- public open_issues!: i32;
119
- public watchers!: i32;
114
+ public forks!: number;
115
+ public open_issues!: number;
116
+ public watchers!: number;
120
117
  public default_branch!: string;
121
118
  }
122
119
 
123
- const v1: Repo = {
120
+ let v1: Repo = {
124
121
  id: 132935648,
125
122
  node_id: "MDEwOlJlcG9zaXRvcnkxMzI5MzU2NDg=",
126
123
  name: "boysenberry-repo-1",
@@ -145,7 +142,7 @@ const v1: Repo = {
145
142
  received_events_url: "https://api.github.com/users/octocat/received_events",
146
143
  type: "User",
147
144
  user_view_type: "public",
148
- site_admin: false,
145
+ site_admin: false
149
146
  },
150
147
  html_url: "https://github.com/octocat/boysenberry-repo-1",
151
148
  description: "Testing",
@@ -219,26 +216,26 @@ const v1: Repo = {
219
216
  forks: 20,
220
217
  open_issues: 1,
221
218
  watchers: 332,
222
- default_branch: "master",
223
- };
219
+ default_branch: "master"
220
+ }
224
221
 
225
222
  const v2 = `{"id":132935648,"node_id":"MDEwOlJlcG9zaXRvcnkxMzI5MzU2NDg=","name":"boysenberry-repo-1","full_name":"octocat/boysenberry-repo-1","private":true,"owner":{"login":"octocat","id":583231,"node_id":"MDQ6VXNlcjU4MzIzMQ==","avatar_url":"https://avatars.githubusercontent.com/u/583231?v=4","gravatar_id":"","url":"https://api.github.com/users/octocat","html_url":"https://github.com/octocat","followers_url":"https://api.github.com/users/octocat/followers","following_url":"https://api.github.com/users/octocat/following{/other_user}","gists_url":"https://api.github.com/users/octocat/gists{/gist_id}","starred_url":"https://api.github.com/users/octocat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/octocat/subscriptions","organizations_url":"https://api.github.com/users/octocat/orgs","repos_url":"https://api.github.com/users/octocat/repos","events_url":"https://api.github.com/users/octocat/events{/privacy}","received_events_url":"https://api.github.com/users/octocat/received_events","type":"User","user_view_type":"public","site_admin":false},"html_url":"https://github.com/octocat/boysenberry-repo-1","description":"Testing","fork":true,"url":"https://api.github.com/repos/octocat/boysenberry-repo-1","forks_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/forks","keys_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/keys{/key_id}","collaborators_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/teams","hooks_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/hooks","issue_events_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/issues/events{/number}","events_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/events","assignees_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/assignees{/user}","branches_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/branches{/branch}","tags_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/tags","blobs_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/git/refs{/sha}","trees_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/git/trees{/sha}","statuses_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/statuses/{sha}","languages_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/languages","stargazers_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/stargazers","contributors_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/contributors","subscribers_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/subscribers","subscription_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/subscription","commits_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/commits{/sha}","git_commits_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/git/commits{/sha}","comments_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/comments{/number}","issue_comment_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/issues/comments{/number}","contents_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/contents/{+path}","compare_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/compare/{base}...{head}","merges_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/merges","archive_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/downloads","issues_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/issues{/number}","pulls_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/pulls{/number}","milestones_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/milestones{/number}","notifications_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/labels{/name}","releases_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/releases{/id}","deployments_url":"https://api.github.com/repos/octocat/boysenberry-repo-1/deployments","created_at":"2018-05-10T17:51:29Z","updated_at":"2025-05-24T02:01:19Z","pushed_at":"2024-05-26T07:02:05Z","git_url":"git://github.com/octocat/boysenberry-repo-1.git","ssh_url":"git@github.com:octocat/boysenberry-repo-1.git","clone_url":"https://github.com/octocat/boysenberry-repo-1.git","svn_url":"https://github.com/octocat/boysenberry-repo-1","homepage":"","size":4,"stargazers_count":332,"watchers_count":332,"language":null,"has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"has_discussions":false,"forks_count":20,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"allow_forking":true,"is_template":false,"web_commit_signoff_required":false,"topics":[],"visibility":"public","forks":20,"open_issues":1,"watchers":332,"default_branch":"master"}`;
226
223
 
227
224
  expect(JSON.stringify(v1)).toBe(v2);
228
- expect(JSON.stringify(JSON.parse<Repo>(v2))).toBe(v2);
225
+ expect(JSON.stringify(JSON.parse(v2))).toBe(v2);
229
226
 
230
227
  bench(
231
228
  "Serialize Large Object",
232
229
  () => {
233
230
  JSON.stringify(v1);
234
231
  },
235
- 100_00,
232
+ 1_000_000,
236
233
  );
237
234
 
238
235
  bench(
239
236
  "Deserialize Large Object",
240
237
  () => {
241
- JSON.parse<Repo>(v2);
238
+ JSON.parse(v2);
242
239
  },
243
- 100_00,
244
- );
240
+ 1_000_000,
241
+ );
@@ -13,7 +13,7 @@ export function expect<T>(left: T): Expectation {
13
13
  // @ts-ignore
14
14
  if (!isDefined(left.toString)) throw new Error("Expected left to have a toString method, but it does not.");
15
15
  // @ts-ignore
16
- return new Expectation(left.toString());
16
+ return new Expectation(isNull(left) ? "null" : left.toString());
17
17
  }
18
18
 
19
19
  class Expectation {
@@ -26,12 +26,16 @@ class Expectation {
26
26
  // @ts-ignore
27
27
  if (!isDefined(right.toString)) throw new Error("Expected right to have a toString method, but it does not.");
28
28
  // @ts-ignore
29
- if (this.left != right.toString()) {
29
+ if (this.left != (isNull(right) ? "null" : right.toString())) {
30
30
  console.log(" " + currentDescription + "\n");
31
31
  // @ts-ignore
32
- console.log(" (expected) -> " + right.toString());
32
+ console.log(" (expected) -> " + (isNull(right) ? "null" : right.toString()));
33
33
  console.log(" (received) -> " + this.left);
34
34
  unreachable();
35
35
  }
36
36
  }
37
37
  }
38
+
39
+ function isNull<T>(value: T): boolean {
40
+ return isInteger<T>() && !isSigned<T>() && nameof<T>() == "usize" && value == 0;
41
+ }
package/assembly/index.ts CHANGED
@@ -603,6 +603,8 @@ export namespace JSON {
603
603
  if (isDefined(type.__DESERIALIZE)) {
604
604
  const out = changetype<nonnull<T>>(dst || __new(offsetof<nonnull<T>>(), idof<nonnull<T>>()));
605
605
  // @ts-ignore: Defined by transform
606
+ if (isDefined(type.__INITIALIZE)) out.__INITIALIZE();
607
+ // @ts-ignore: Defined by transform
606
608
  return out.__DESERIALIZE(srcStart, srcEnd, out);
607
609
  } else if (type instanceof Map) {
608
610
  // @ts-ignore: type
package/assembly/test.ts CHANGED
@@ -40,18 +40,143 @@ it("should parse", () => {
40
40
  }`;
41
41
 
42
42
  const output = JSON.parse<OpenAIChatOutput>(str);
43
-
44
43
  expect(output.id).toBe("chatcmpl-BbvlnP0ESWa8OForeEjt7AkoIuh3Q");
45
44
  expect(output.object).toBe("chat.completion");
45
+ expect(output.created.getTime()).toBe(1748379903000);
46
46
  expect(output.model).toBe("gpt-4o-mini-2024-07-18");
47
- expect(output.system_fingerprint).toBe("fp_34a54ae93c"); // fails ("")
48
- });
47
+ expect(output.choices.length).toBe(1);
49
48
 
49
+ const choice = output.choices[0];
50
+ expect(choice.index).toBe(0);
51
+ expect(choice.message.content).toBe("Hello! How can I assist you today?");
52
+ // expect(choice.message.refusal).toBe("null");
53
+ // expect(choice.logprobs).toBe("null");
54
+ // expect(choice.finishReason).toBe("stop");
55
+
56
+ // expect(output.usage.promptTokens).toBe(15);
57
+ // expect(output.usage.completionTokens).toBe(9);
58
+ // expect(output.usage.totalTokens).toBe(24);
59
+ // expect(output.serviceTier).toBe("default");
60
+ // expect(output.systemFingerprint).toBe("fp_34a54ae93c");
61
+ });
50
62
 
51
63
  @json
52
64
  class OpenAIChatOutput {
53
65
  id!: string;
66
+
54
67
  object!: string;
68
+
69
+ choices: Choice[] = [];
70
+
71
+ get created(): Date {
72
+ return new Date(this._created * 1000);
73
+ }
74
+
75
+ @alias("created")
76
+ private _created!: i64;
77
+
55
78
  model!: string;
56
- system_fingerprint!: string;
79
+
80
+ @alias("service_tier")
81
+ serviceTier: string | null = null;
82
+
83
+ @alias("system_fingerprint")
84
+ systemFingerprint!: string;
85
+
86
+ usage!: Usage;
87
+ }
88
+
89
+ @json
90
+ class ToolCall {
91
+ id!: string;
92
+
93
+ type: string = "function";
94
+
95
+ function!: FunctionCall;
96
+ }
97
+
98
+ @json
99
+ class FunctionCall {
100
+ name!: string;
101
+
102
+ arguments!: string;
57
103
  }
104
+
105
+ @json
106
+ class Usage {
107
+ @alias("completion_tokens")
108
+ completionTokens!: i32;
109
+
110
+ @alias("prompt_tokens")
111
+ promptTokens!: i32;
112
+
113
+ @alias("total_tokens")
114
+ totalTokens!: i32;
115
+ }
116
+
117
+ @json
118
+ class Choice {
119
+ @alias("finish_reason")
120
+ finishReason!: string;
121
+
122
+ index!: i32;
123
+
124
+ message: CompletionMessage = new CompletionMessage();
125
+
126
+ logprobs!: Logprobs | null;
127
+ }
128
+
129
+ @json
130
+ class Logprobs {
131
+ content: LogprobsContent[] | null = null;
132
+ }
133
+
134
+ @json
135
+ class LogprobsContent {
136
+ token!: string;
137
+
138
+ logprob!: f64;
139
+
140
+ bytes!: u8[] | null;
141
+
142
+ @alias("top_logprobs")
143
+ topLogprobs!: TopLogprobsContent[];
144
+ }
145
+
146
+ @json
147
+ class TopLogprobsContent {
148
+ token!: string;
149
+
150
+ logprob!: f64;
151
+
152
+ bytes!: u8[] | null;
153
+ }
154
+
155
+ @json
156
+ class CompletionMessage {
157
+ content!: string;
158
+
159
+ @omitnull()
160
+ refusal: string | null = null;
161
+
162
+ @alias("tool_calls")
163
+ @omitif((self: CompletionMessage) => self.toolCalls.length == 0)
164
+ toolCalls: ToolCall[] = [];
165
+
166
+ @omitnull()
167
+ audio: AudioOutput | null = null;
168
+ }
169
+
170
+ @json
171
+ class AudioOutput {
172
+ id!: string;
173
+
174
+ get expiresAt(): Date {
175
+ return new Date(this._expiresAt * 1000);
176
+ }
177
+
178
+ @alias("expires_at")
179
+ private _expiresAt!: i64;
180
+
181
+ transcript!: string;
182
+ }
package/assembly/types.ts CHANGED
@@ -0,0 +1,70 @@
1
+ import { JSON } from "."
2
+
3
+ @json
4
+ export class GenericEnum<T> {
5
+ private tag: string = ""
6
+ private value: T | null = null
7
+
8
+ constructor() {
9
+ this.tag = ""
10
+ this.value = null
11
+ }
12
+
13
+ static create<T>(tag: string, value: T): GenericEnum<T> {
14
+ const item = new GenericEnum<T>()
15
+ item.tag = tag
16
+ item.value = value
17
+ return item
18
+ }
19
+
20
+ getTag(): string {
21
+ return this.tag
22
+ }
23
+
24
+ getValue(): T | null {
25
+ return this.value
26
+ }
27
+ @serializer
28
+ serialize<T>(self: GenericEnum<T>): string {
29
+ const tagJson = JSON.stringify(self.tag);
30
+ const valueJson = JSON.stringify(self.value);
31
+ return `{${tagJson}:${valueJson}}`
32
+ }
33
+ @deserializer
34
+ deserialize(data: string): GenericEnum<T> {
35
+ const parsed = JSON.parse<Map<string, JSON.Raw>>(data);
36
+ const result = new GenericEnum<T>();
37
+
38
+ const keys = parsed.keys();
39
+ const values = parsed.values();
40
+
41
+ result.tag = keys[0];
42
+ result.value = JSON.parse<T>(values[0].data);
43
+
44
+ return result;
45
+ }
46
+ }
47
+
48
+ @json
49
+ export class Node<T> {
50
+ name: string
51
+ id: u32
52
+ data: T
53
+
54
+ constructor() {
55
+ this.name = ""
56
+ this.id = 0
57
+ this.data = changetype<T>(0);
58
+ }
59
+ }
60
+
61
+ @json
62
+ export class Vec3 {
63
+ x: f32 = 0.0;
64
+ y: f32 = 0.0;
65
+ z: f32 = 0.0;
66
+ }
67
+
68
+
69
+ @json
70
+ export class Point { }