@macroforge/mcp-server 0.1.36 → 0.1.38
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/docs/builtin-macros/clone.md +43 -23
- package/docs/builtin-macros/debug.md +50 -18
- package/docs/builtin-macros/default.md +79 -28
- package/docs/builtin-macros/deserialize/cycleforward-reference-support.md +11 -0
- package/docs/builtin-macros/deserialize/example.md +1625 -0
- package/docs/builtin-macros/deserialize/overview.md +15 -10
- package/docs/builtin-macros/deserialize/union-type-deserialization.md +27 -0
- package/docs/builtin-macros/deserialize/validation.md +34 -0
- package/docs/builtin-macros/deserialize.md +1608 -23
- package/docs/builtin-macros/hash.md +87 -20
- package/docs/builtin-macros/ord.md +56 -31
- package/docs/builtin-macros/partial-eq/example.md +526 -0
- package/docs/builtin-macros/partial-eq/overview.md +39 -0
- package/docs/builtin-macros/partial-eq.md +184 -26
- package/docs/builtin-macros/partial-ord.md +68 -30
- package/docs/builtin-macros/serialize/example.md +139 -0
- package/docs/builtin-macros/serialize/overview.md +32 -0
- package/docs/builtin-macros/serialize/type-specific-serialization.md +22 -0
- package/docs/builtin-macros/serialize.md +130 -28
- package/docs/concepts/derive-system.md +33 -42
- package/docs/concepts/how-macros-work.md +8 -4
- package/docs/getting-started/first-macro.md +36 -26
- package/docs/sections.json +88 -2
- package/package.json +2 -2
|
@@ -0,0 +1,1625 @@
|
|
|
1
|
+
## Example
|
|
2
|
+
|
|
3
|
+
```typescript before
|
|
4
|
+
/** @derive(Deserialize) @serde({ denyUnknownFields: true }) */
|
|
5
|
+
class User {
|
|
6
|
+
id: number;
|
|
7
|
+
|
|
8
|
+
/** @serde({ validate: { email: true, maxLength: 255 } }) */
|
|
9
|
+
email: string;
|
|
10
|
+
|
|
11
|
+
/** @serde({ default: "guest" }) */
|
|
12
|
+
name: string;
|
|
13
|
+
|
|
14
|
+
/** @serde({ validate: { positive: true } }) */
|
|
15
|
+
age?: number;
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```typescript after
|
|
20
|
+
class User {
|
|
21
|
+
id: number;
|
|
22
|
+
|
|
23
|
+
email: string;
|
|
24
|
+
|
|
25
|
+
name: string;
|
|
26
|
+
|
|
27
|
+
age?: number;
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Generated output:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
class User {
|
|
35
|
+
id: number;
|
|
36
|
+
|
|
37
|
+
email: string;
|
|
38
|
+
|
|
39
|
+
name: string;
|
|
40
|
+
|
|
41
|
+
age?: number;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Generated output:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { DeserializeContext } from 'macroforge/serde';
|
|
49
|
+
import { DeserializeError } from 'macroforge/serde';
|
|
50
|
+
import type { DeserializeOptions } from 'macroforge/serde';
|
|
51
|
+
import { PendingRef } from 'macroforge/serde';
|
|
52
|
+
|
|
53
|
+
/** @serde({ denyUnknownFields: true }) */
|
|
54
|
+
class User {
|
|
55
|
+
id: number;
|
|
56
|
+
|
|
57
|
+
email: string;
|
|
58
|
+
|
|
59
|
+
name: string;
|
|
60
|
+
|
|
61
|
+
age?: number;
|
|
62
|
+
|
|
63
|
+
constructor(props: {
|
|
64
|
+
id: number;
|
|
65
|
+
email: string;
|
|
66
|
+
name?: string;
|
|
67
|
+
age?: number;
|
|
68
|
+
}) {
|
|
69
|
+
this.id = props.id;
|
|
70
|
+
this.email = props.email;
|
|
71
|
+
this.name = props.name as string;
|
|
72
|
+
this.age = props.age as number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Deserializes input to an instance of this class.
|
|
77
|
+
* Automatically detects whether input is a JSON string or object.
|
|
78
|
+
* @param input - JSON string or object to deserialize
|
|
79
|
+
* @param opts - Optional deserialization options
|
|
80
|
+
* @returns Result containing the deserialized instance or validation errors
|
|
81
|
+
*/
|
|
82
|
+
static deserialize(
|
|
83
|
+
input: unknown,
|
|
84
|
+
opts?: DeserializeOptions
|
|
85
|
+
): Result<
|
|
86
|
+
User,
|
|
87
|
+
Array<{
|
|
88
|
+
field: string;
|
|
89
|
+
message: string;
|
|
90
|
+
}>
|
|
91
|
+
> {
|
|
92
|
+
try {
|
|
93
|
+
// Auto-detect: if string, parse as JSON first
|
|
94
|
+
const data = typeof input === 'string' ? JSON.parse(input) : input;
|
|
95
|
+
|
|
96
|
+
const ctx = DeserializeContext.create();
|
|
97
|
+
const resultOrRef = User.deserializeWithContext(data, ctx);
|
|
98
|
+
if (PendingRef.is(resultOrRef)) {
|
|
99
|
+
return Result.err([
|
|
100
|
+
{
|
|
101
|
+
field: '_root',
|
|
102
|
+
message: 'User.deserialize: root cannot be a forward reference'
|
|
103
|
+
}
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
ctx.applyPatches();
|
|
107
|
+
if (opts?.freeze) {
|
|
108
|
+
ctx.freezeAll();
|
|
109
|
+
}
|
|
110
|
+
return Result.ok(resultOrRef);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
if (e instanceof DeserializeError) {
|
|
113
|
+
return Result.err(e.errors);
|
|
114
|
+
}
|
|
115
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
116
|
+
return Result.err([
|
|
117
|
+
{
|
|
118
|
+
field: '_root',
|
|
119
|
+
message
|
|
120
|
+
}
|
|
121
|
+
]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** @internal */
|
|
126
|
+
static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
|
|
127
|
+
if (value?.__ref !== undefined) {
|
|
128
|
+
return ctx.getOrDefer(value.__ref);
|
|
129
|
+
}
|
|
130
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
131
|
+
throw new DeserializeError([
|
|
132
|
+
{
|
|
133
|
+
field: '_root',
|
|
134
|
+
message: 'User.deserializeWithContext: expected an object'
|
|
135
|
+
}
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
const obj = value as Record<string, unknown>;
|
|
139
|
+
const errors: Array<{
|
|
140
|
+
field: string;
|
|
141
|
+
message: string;
|
|
142
|
+
}> = [];
|
|
143
|
+
const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
|
|
144
|
+
for (const key of Object.keys(obj)) {
|
|
145
|
+
if (!knownKeys.has(key)) {
|
|
146
|
+
errors.push({
|
|
147
|
+
field: key,
|
|
148
|
+
message: 'unknown field'
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!('id' in obj)) {
|
|
153
|
+
errors.push({
|
|
154
|
+
field: 'id',
|
|
155
|
+
message: 'missing required field'
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (!('email' in obj)) {
|
|
159
|
+
errors.push({
|
|
160
|
+
field: 'email',
|
|
161
|
+
message: 'missing required field'
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (errors.length > 0) {
|
|
165
|
+
throw new DeserializeError(errors);
|
|
166
|
+
}
|
|
167
|
+
const instance = Object.create(User.prototype) as User;
|
|
168
|
+
if (obj.__id !== undefined) {
|
|
169
|
+
ctx.register(obj.__id as number, instance);
|
|
170
|
+
}
|
|
171
|
+
ctx.trackForFreeze(instance);
|
|
172
|
+
{
|
|
173
|
+
const __raw_id = obj['id'] as number;
|
|
174
|
+
instance.id = __raw_id;
|
|
175
|
+
}
|
|
176
|
+
{
|
|
177
|
+
const __raw_email = obj['email'] as string;
|
|
178
|
+
instance.email = __raw_email;
|
|
179
|
+
}
|
|
180
|
+
if ('name' in obj && obj['name'] !== undefined) {
|
|
181
|
+
const __raw_name = obj['name'] as string;
|
|
182
|
+
instance.name = __raw_name;
|
|
183
|
+
} else {
|
|
184
|
+
instance.name = "guest";
|
|
185
|
+
}
|
|
186
|
+
if ('age' in obj && obj['age'] !== undefined) {
|
|
187
|
+
const __raw_age = obj['age'] as number;
|
|
188
|
+
instance.age = __raw_age;
|
|
189
|
+
}
|
|
190
|
+
if (errors.length > 0) {
|
|
191
|
+
throw new DeserializeError(errors);
|
|
192
|
+
}
|
|
193
|
+
return instance;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
static validateField<K extends keyof User>(
|
|
197
|
+
field: K,
|
|
198
|
+
value: User[K]
|
|
199
|
+
): Array<{
|
|
200
|
+
field: string;
|
|
201
|
+
message: string;
|
|
202
|
+
}> {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
static validateFields(partial: Partial<User>): Array<{
|
|
207
|
+
field: string;
|
|
208
|
+
message: string;
|
|
209
|
+
}> {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
static hasShape(obj: unknown): boolean {
|
|
214
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
const o = obj as Record<string, unknown>;
|
|
218
|
+
return 'id' in o && 'email' in o;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static is(obj: unknown): obj is User {
|
|
222
|
+
if (obj instanceof User) {
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
if (!User.hasShape(obj)) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const result = User.deserialize(obj);
|
|
229
|
+
return Result.isOk(result);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Usage:
|
|
234
|
+
const result = User.deserialize('{"id":1,"email":"test@example.com"}');
|
|
235
|
+
if (Result.isOk(result)) {
|
|
236
|
+
const user = result.value;
|
|
237
|
+
} else {
|
|
238
|
+
console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Generated output:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { DeserializeContext } from 'macroforge/serde';
|
|
246
|
+
import { DeserializeError } from 'macroforge/serde';
|
|
247
|
+
import type { DeserializeOptions } from 'macroforge/serde';
|
|
248
|
+
import { PendingRef } from 'macroforge/serde';
|
|
249
|
+
|
|
250
|
+
/** @serde({ denyUnknownFields: true }) */
|
|
251
|
+
class User {
|
|
252
|
+
id: number;
|
|
253
|
+
|
|
254
|
+
email: string;
|
|
255
|
+
|
|
256
|
+
name: string;
|
|
257
|
+
|
|
258
|
+
age?: number;
|
|
259
|
+
|
|
260
|
+
constructor(props: {
|
|
261
|
+
id: number;
|
|
262
|
+
email: string;
|
|
263
|
+
name?: string;
|
|
264
|
+
age?: number;
|
|
265
|
+
}) {
|
|
266
|
+
this.id = props.id;
|
|
267
|
+
this.email = props.email;
|
|
268
|
+
this.name = props.name as string;
|
|
269
|
+
this.age = props.age as number;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Deserializes input to an instance of this class.
|
|
274
|
+
* Automatically detects whether input is a JSON string or object.
|
|
275
|
+
* @param input - JSON string or object to deserialize
|
|
276
|
+
* @param opts - Optional deserialization options
|
|
277
|
+
* @returns Result containing the deserialized instance or validation errors
|
|
278
|
+
*/
|
|
279
|
+
static deserialize(
|
|
280
|
+
input: unknown,
|
|
281
|
+
opts?: DeserializeOptions
|
|
282
|
+
): Result<
|
|
283
|
+
User,
|
|
284
|
+
Array<{
|
|
285
|
+
field: string;
|
|
286
|
+
message: string;
|
|
287
|
+
}>
|
|
288
|
+
> {
|
|
289
|
+
try {
|
|
290
|
+
// Auto-detect: if string, parse as JSON first
|
|
291
|
+
const data = typeof input === 'string' ? JSON.parse(input) : input;
|
|
292
|
+
|
|
293
|
+
const ctx = DeserializeContext.create();
|
|
294
|
+
const resultOrRef = User.deserializeWithContext(data, ctx);
|
|
295
|
+
if (PendingRef.is(resultOrRef)) {
|
|
296
|
+
return Result.err([
|
|
297
|
+
{
|
|
298
|
+
field: '_root',
|
|
299
|
+
message: 'User.deserialize: root cannot be a forward reference'
|
|
300
|
+
}
|
|
301
|
+
]);
|
|
302
|
+
}
|
|
303
|
+
ctx.applyPatches();
|
|
304
|
+
if (opts?.freeze) {
|
|
305
|
+
ctx.freezeAll();
|
|
306
|
+
}
|
|
307
|
+
return Result.ok(resultOrRef);
|
|
308
|
+
} catch (e) {
|
|
309
|
+
if (e instanceof DeserializeError) {
|
|
310
|
+
return Result.err(e.errors);
|
|
311
|
+
}
|
|
312
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
313
|
+
return Result.err([
|
|
314
|
+
{
|
|
315
|
+
field: '_root',
|
|
316
|
+
message
|
|
317
|
+
}
|
|
318
|
+
]);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** @internal */
|
|
323
|
+
static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
|
|
324
|
+
if (value?.__ref !== undefined) {
|
|
325
|
+
return ctx.getOrDefer(value.__ref);
|
|
326
|
+
}
|
|
327
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
328
|
+
throw new DeserializeError([
|
|
329
|
+
{
|
|
330
|
+
field: '_root',
|
|
331
|
+
message: 'User.deserializeWithContext: expected an object'
|
|
332
|
+
}
|
|
333
|
+
]);
|
|
334
|
+
}
|
|
335
|
+
const obj = value as Record<string, unknown>;
|
|
336
|
+
const errors: Array<{
|
|
337
|
+
field: string;
|
|
338
|
+
message: string;
|
|
339
|
+
}> = [];
|
|
340
|
+
const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
|
|
341
|
+
for (const key of Object.keys(obj)) {
|
|
342
|
+
if (!knownKeys.has(key)) {
|
|
343
|
+
errors.push({
|
|
344
|
+
field: key,
|
|
345
|
+
message: 'unknown field'
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (!('id' in obj)) {
|
|
350
|
+
errors.push({
|
|
351
|
+
field: 'id',
|
|
352
|
+
message: 'missing required field'
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
if (!('email' in obj)) {
|
|
356
|
+
errors.push({
|
|
357
|
+
field: 'email',
|
|
358
|
+
message: 'missing required field'
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
if (errors.length > 0) {
|
|
362
|
+
throw new DeserializeError(errors);
|
|
363
|
+
}
|
|
364
|
+
const instance = Object.create(User.prototype) as User;
|
|
365
|
+
if (obj.__id !== undefined) {
|
|
366
|
+
ctx.register(obj.__id as number, instance);
|
|
367
|
+
}
|
|
368
|
+
ctx.trackForFreeze(instance);
|
|
369
|
+
{
|
|
370
|
+
const __raw_id = obj['id'] as number;
|
|
371
|
+
instance.id = __raw_id;
|
|
372
|
+
}
|
|
373
|
+
{
|
|
374
|
+
const __raw_email = obj['email'] as string;
|
|
375
|
+
instance.email = __raw_email;
|
|
376
|
+
}
|
|
377
|
+
if ('name' in obj && obj['name'] !== undefined) {
|
|
378
|
+
const __raw_name = obj['name'] as string;
|
|
379
|
+
instance.name = __raw_name;
|
|
380
|
+
} else {
|
|
381
|
+
instance.name = 'guest';
|
|
382
|
+
}
|
|
383
|
+
if ('age' in obj && obj['age'] !== undefined) {
|
|
384
|
+
const __raw_age = obj['age'] as number;
|
|
385
|
+
instance.age = __raw_age;
|
|
386
|
+
}
|
|
387
|
+
if (errors.length > 0) {
|
|
388
|
+
throw new DeserializeError(errors);
|
|
389
|
+
}
|
|
390
|
+
return instance;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
static validateField<K extends keyof User>(
|
|
394
|
+
field: K,
|
|
395
|
+
value: User[K]
|
|
396
|
+
): Array<{
|
|
397
|
+
field: string;
|
|
398
|
+
message: string;
|
|
399
|
+
}> {
|
|
400
|
+
return [];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
static validateFields(partial: Partial<User>): Array<{
|
|
404
|
+
field: string;
|
|
405
|
+
message: string;
|
|
406
|
+
}> {
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
static hasShape(obj: unknown): boolean {
|
|
411
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
const o = obj as Record<string, unknown>;
|
|
415
|
+
return 'id' in o && 'email' in o;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
static is(obj: unknown): obj is User {
|
|
419
|
+
if (obj instanceof User) {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
if (!User.hasShape(obj)) {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
const result = User.deserialize(obj);
|
|
426
|
+
return Result.isOk(result);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Usage:
|
|
431
|
+
const result = User.deserialize('{"id":1,"email":"test@example.com"}');
|
|
432
|
+
if (Result.isOk(result)) {
|
|
433
|
+
const user = result.value;
|
|
434
|
+
} else {
|
|
435
|
+
console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Generated output:
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import { DeserializeContext } from 'macroforge/serde';
|
|
443
|
+
import { DeserializeError } from 'macroforge/serde';
|
|
444
|
+
import type { DeserializeOptions } from 'macroforge/serde';
|
|
445
|
+
import { PendingRef } from 'macroforge/serde';
|
|
446
|
+
|
|
447
|
+
/** @serde({ denyUnknownFields: true }) */
|
|
448
|
+
class User {
|
|
449
|
+
id: number;
|
|
450
|
+
|
|
451
|
+
email: string;
|
|
452
|
+
|
|
453
|
+
name: string;
|
|
454
|
+
|
|
455
|
+
age?: number;
|
|
456
|
+
|
|
457
|
+
constructor(props: {
|
|
458
|
+
id: number;
|
|
459
|
+
email: string;
|
|
460
|
+
name?: string;
|
|
461
|
+
age?: number;
|
|
462
|
+
}) {
|
|
463
|
+
this.id = props.id;
|
|
464
|
+
this.email = props.email;
|
|
465
|
+
this.name = props.name as string;
|
|
466
|
+
this.age = props.age as number;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Deserializes input to an instance of this class.
|
|
471
|
+
* Automatically detects whether input is a JSON string or object.
|
|
472
|
+
* @param input - JSON string or object to deserialize
|
|
473
|
+
* @param opts - Optional deserialization options
|
|
474
|
+
* @returns Result containing the deserialized instance or validation errors
|
|
475
|
+
*/
|
|
476
|
+
static deserialize(
|
|
477
|
+
input: unknown,
|
|
478
|
+
opts?: DeserializeOptions
|
|
479
|
+
): Result<
|
|
480
|
+
User,
|
|
481
|
+
Array<{
|
|
482
|
+
field: string;
|
|
483
|
+
message: string;
|
|
484
|
+
}>
|
|
485
|
+
> {
|
|
486
|
+
try {
|
|
487
|
+
// Auto-detect: if string, parse as JSON first
|
|
488
|
+
const data = typeof input === 'string' ? JSON.parse(input) : input;
|
|
489
|
+
|
|
490
|
+
const ctx = DeserializeContext.create();
|
|
491
|
+
const resultOrRef = User.deserializeWithContext(data, ctx);
|
|
492
|
+
if (PendingRef.is(resultOrRef)) {
|
|
493
|
+
return Result.err([
|
|
494
|
+
{
|
|
495
|
+
field: '_root',
|
|
496
|
+
message: 'User.deserialize: root cannot be a forward reference'
|
|
497
|
+
}
|
|
498
|
+
]);
|
|
499
|
+
}
|
|
500
|
+
ctx.applyPatches();
|
|
501
|
+
if (opts?.freeze) {
|
|
502
|
+
ctx.freezeAll();
|
|
503
|
+
}
|
|
504
|
+
return Result.ok(resultOrRef);
|
|
505
|
+
} catch (e) {
|
|
506
|
+
if (e instanceof DeserializeError) {
|
|
507
|
+
return Result.err(e.errors);
|
|
508
|
+
}
|
|
509
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
510
|
+
return Result.err([
|
|
511
|
+
{
|
|
512
|
+
field: '_root',
|
|
513
|
+
message
|
|
514
|
+
}
|
|
515
|
+
]);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** @internal */
|
|
520
|
+
static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
|
|
521
|
+
if (value?.__ref !== undefined) {
|
|
522
|
+
return ctx.getOrDefer(value.__ref);
|
|
523
|
+
}
|
|
524
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
525
|
+
throw new DeserializeError([
|
|
526
|
+
{
|
|
527
|
+
field: '_root',
|
|
528
|
+
message: 'User.deserializeWithContext: expected an object'
|
|
529
|
+
}
|
|
530
|
+
]);
|
|
531
|
+
}
|
|
532
|
+
const obj = value as Record<string, unknown>;
|
|
533
|
+
const errors: Array<{
|
|
534
|
+
field: string;
|
|
535
|
+
message: string;
|
|
536
|
+
}> = [];
|
|
537
|
+
const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
|
|
538
|
+
for (const key of Object.keys(obj)) {
|
|
539
|
+
if (!knownKeys.has(key)) {
|
|
540
|
+
errors.push({
|
|
541
|
+
field: key,
|
|
542
|
+
message: 'unknown field'
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (!('id' in obj)) {
|
|
547
|
+
errors.push({
|
|
548
|
+
field: 'id',
|
|
549
|
+
message: 'missing required field'
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (!('email' in obj)) {
|
|
553
|
+
errors.push({
|
|
554
|
+
field: 'email',
|
|
555
|
+
message: 'missing required field'
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (errors.length > 0) {
|
|
559
|
+
throw new DeserializeError(errors);
|
|
560
|
+
}
|
|
561
|
+
const instance = Object.create(User.prototype) as User;
|
|
562
|
+
if (obj.__id !== undefined) {
|
|
563
|
+
ctx.register(obj.__id as number, instance);
|
|
564
|
+
}
|
|
565
|
+
ctx.trackForFreeze(instance);
|
|
566
|
+
{
|
|
567
|
+
const __raw_id = obj['id'] as number;
|
|
568
|
+
instance.id = __raw_id;
|
|
569
|
+
}
|
|
570
|
+
{
|
|
571
|
+
const __raw_email = obj['email'] as string;
|
|
572
|
+
instance.email = __raw_email;
|
|
573
|
+
}
|
|
574
|
+
if ('name' in obj && obj['name'] !== undefined) {
|
|
575
|
+
const __raw_name = obj['name'] as string;
|
|
576
|
+
instance.name = __raw_name;
|
|
577
|
+
} else {
|
|
578
|
+
instance.name = "guest";
|
|
579
|
+
}
|
|
580
|
+
if ('age' in obj && obj['age'] !== undefined) {
|
|
581
|
+
const __raw_age = obj['age'] as number;
|
|
582
|
+
instance.age = __raw_age;
|
|
583
|
+
}
|
|
584
|
+
if (errors.length > 0) {
|
|
585
|
+
throw new DeserializeError(errors);
|
|
586
|
+
}
|
|
587
|
+
return instance;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
static validateField<K extends keyof User>(
|
|
591
|
+
field: K,
|
|
592
|
+
value: User[K]
|
|
593
|
+
): Array<{
|
|
594
|
+
field: string;
|
|
595
|
+
message: string;
|
|
596
|
+
}> {
|
|
597
|
+
return [];
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
static validateFields(partial: Partial<User>): Array<{
|
|
601
|
+
field: string;
|
|
602
|
+
message: string;
|
|
603
|
+
}> {
|
|
604
|
+
return [];
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
static hasShape(obj: unknown): boolean {
|
|
608
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
const o = obj as Record<string, unknown>;
|
|
612
|
+
return 'id' in o && 'email' in o;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
static is(obj: unknown): obj is User {
|
|
616
|
+
if (obj instanceof User) {
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
if (!User.hasShape(obj)) {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
const result = User.deserialize(obj);
|
|
623
|
+
return Result.isOk(result);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Usage:
|
|
628
|
+
const result = User.deserialize('{"id":1,"email":"test@example.com"}');
|
|
629
|
+
if (Result.isOk(result)) {
|
|
630
|
+
const user = result.value;
|
|
631
|
+
} else {
|
|
632
|
+
console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Generated output:
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
import { DeserializeContext } from 'macroforge/serde';
|
|
640
|
+
import { DeserializeError } from 'macroforge/serde';
|
|
641
|
+
import type { DeserializeOptions } from 'macroforge/serde';
|
|
642
|
+
import { PendingRef } from 'macroforge/serde';
|
|
643
|
+
|
|
644
|
+
/** @serde({ denyUnknownFields: true }) */
|
|
645
|
+
class User {
|
|
646
|
+
id: number;
|
|
647
|
+
|
|
648
|
+
email: string;
|
|
649
|
+
|
|
650
|
+
name: string;
|
|
651
|
+
|
|
652
|
+
age?: number;
|
|
653
|
+
|
|
654
|
+
constructor(props: {
|
|
655
|
+
id: number;
|
|
656
|
+
email: string;
|
|
657
|
+
name?: string;
|
|
658
|
+
age?: number;
|
|
659
|
+
}) {
|
|
660
|
+
this.id = props.id;
|
|
661
|
+
this.email = props.email;
|
|
662
|
+
this.name = props.name as string;
|
|
663
|
+
this.age = props.age as number;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Deserializes input to an instance of this class.
|
|
668
|
+
* Automatically detects whether input is a JSON string or object.
|
|
669
|
+
* @param input - JSON string or object to deserialize
|
|
670
|
+
* @param opts - Optional deserialization options
|
|
671
|
+
* @returns Result containing the deserialized instance or validation errors
|
|
672
|
+
*/
|
|
673
|
+
static deserialize(
|
|
674
|
+
input: unknown,
|
|
675
|
+
opts?: DeserializeOptions
|
|
676
|
+
): Result<
|
|
677
|
+
User,
|
|
678
|
+
Array<{
|
|
679
|
+
field: string;
|
|
680
|
+
message: string;
|
|
681
|
+
}>
|
|
682
|
+
> {
|
|
683
|
+
try {
|
|
684
|
+
// Auto-detect: if string, parse as JSON first
|
|
685
|
+
const data = typeof input === 'string' ? JSON.parse(input) : input;
|
|
686
|
+
|
|
687
|
+
const ctx = DeserializeContext.create();
|
|
688
|
+
const resultOrRef = User.deserializeWithContext(data, ctx);
|
|
689
|
+
if (PendingRef.is(resultOrRef)) {
|
|
690
|
+
return Result.err([
|
|
691
|
+
{
|
|
692
|
+
field: '_root',
|
|
693
|
+
message: 'User.deserialize: root cannot be a forward reference'
|
|
694
|
+
}
|
|
695
|
+
]);
|
|
696
|
+
}
|
|
697
|
+
ctx.applyPatches();
|
|
698
|
+
if (opts?.freeze) {
|
|
699
|
+
ctx.freezeAll();
|
|
700
|
+
}
|
|
701
|
+
return Result.ok(resultOrRef);
|
|
702
|
+
} catch (e) {
|
|
703
|
+
if (e instanceof DeserializeError) {
|
|
704
|
+
return Result.err(e.errors);
|
|
705
|
+
}
|
|
706
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
707
|
+
return Result.err([
|
|
708
|
+
{
|
|
709
|
+
field: '_root',
|
|
710
|
+
message
|
|
711
|
+
}
|
|
712
|
+
]);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/** @internal */
|
|
717
|
+
static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
|
|
718
|
+
if (value?.__ref !== undefined) {
|
|
719
|
+
return ctx.getOrDefer(value.__ref);
|
|
720
|
+
}
|
|
721
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
722
|
+
throw new DeserializeError([
|
|
723
|
+
{
|
|
724
|
+
field: '_root',
|
|
725
|
+
message: 'User.deserializeWithContext: expected an object'
|
|
726
|
+
}
|
|
727
|
+
]);
|
|
728
|
+
}
|
|
729
|
+
const obj = value as Record<string, unknown>;
|
|
730
|
+
const errors: Array<{
|
|
731
|
+
field: string;
|
|
732
|
+
message: string;
|
|
733
|
+
}> = [];
|
|
734
|
+
const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
|
|
735
|
+
for (const key of Object.keys(obj)) {
|
|
736
|
+
if (!knownKeys.has(key)) {
|
|
737
|
+
errors.push({
|
|
738
|
+
field: key,
|
|
739
|
+
message: 'unknown field'
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (!('id' in obj)) {
|
|
744
|
+
errors.push({
|
|
745
|
+
field: 'id',
|
|
746
|
+
message: 'missing required field'
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
if (!('email' in obj)) {
|
|
750
|
+
errors.push({
|
|
751
|
+
field: 'email',
|
|
752
|
+
message: 'missing required field'
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
if (errors.length > 0) {
|
|
756
|
+
throw new DeserializeError(errors);
|
|
757
|
+
}
|
|
758
|
+
const instance = Object.create(User.prototype) as User;
|
|
759
|
+
if (obj.__id !== undefined) {
|
|
760
|
+
ctx.register(obj.__id as number, instance);
|
|
761
|
+
}
|
|
762
|
+
ctx.trackForFreeze(instance);
|
|
763
|
+
{
|
|
764
|
+
const __raw_id = obj['id'] as number;
|
|
765
|
+
instance.id = __raw_id;
|
|
766
|
+
}
|
|
767
|
+
{
|
|
768
|
+
const __raw_email = obj['email'] as string;
|
|
769
|
+
instance.email = __raw_email;
|
|
770
|
+
}
|
|
771
|
+
if ('name' in obj && obj['name'] !== undefined) {
|
|
772
|
+
const __raw_name = obj['name'] as string;
|
|
773
|
+
instance.name = __raw_name;
|
|
774
|
+
} else {
|
|
775
|
+
instance.name = 'guest';
|
|
776
|
+
}
|
|
777
|
+
if ('age' in obj && obj['age'] !== undefined) {
|
|
778
|
+
const __raw_age = obj['age'] as number;
|
|
779
|
+
instance.age = __raw_age;
|
|
780
|
+
}
|
|
781
|
+
if (errors.length > 0) {
|
|
782
|
+
throw new DeserializeError(errors);
|
|
783
|
+
}
|
|
784
|
+
return instance;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
static validateField<K extends keyof User>(
|
|
788
|
+
field: K,
|
|
789
|
+
value: User[K]
|
|
790
|
+
): Array<{
|
|
791
|
+
field: string;
|
|
792
|
+
message: string;
|
|
793
|
+
}> {
|
|
794
|
+
return [];
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
static validateFields(partial: Partial<User>): Array<{
|
|
798
|
+
field: string;
|
|
799
|
+
message: string;
|
|
800
|
+
}> {
|
|
801
|
+
return [];
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
static hasShape(obj: unknown): boolean {
|
|
805
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
const o = obj as Record<string, unknown>;
|
|
809
|
+
return 'id' in o && 'email' in o;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
static is(obj: unknown): obj is User {
|
|
813
|
+
if (obj instanceof User) {
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
816
|
+
if (!User.hasShape(obj)) {
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
const result = User.deserialize(obj);
|
|
820
|
+
return Result.isOk(result);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Usage:
|
|
825
|
+
const result = User.deserialize('{"id":1,"email":"test@example.com"}');
|
|
826
|
+
if (Result.isOk(result)) {
|
|
827
|
+
const user = result.value;
|
|
828
|
+
} else {
|
|
829
|
+
console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
|
|
830
|
+
}
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
Generated output:
|
|
834
|
+
|
|
835
|
+
```typescript
|
|
836
|
+
import { DeserializeContext } from 'macroforge/serde';
|
|
837
|
+
import { DeserializeError } from 'macroforge/serde';
|
|
838
|
+
import type { DeserializeOptions } from 'macroforge/serde';
|
|
839
|
+
import { PendingRef } from 'macroforge/serde';
|
|
840
|
+
|
|
841
|
+
/** @serde({ denyUnknownFields: true }) */
|
|
842
|
+
class User {
|
|
843
|
+
id: number;
|
|
844
|
+
|
|
845
|
+
email: string;
|
|
846
|
+
|
|
847
|
+
name: string;
|
|
848
|
+
|
|
849
|
+
age?: number;
|
|
850
|
+
|
|
851
|
+
constructor(props: {
|
|
852
|
+
id: number;
|
|
853
|
+
email: string;
|
|
854
|
+
name?: string;
|
|
855
|
+
age?: number;
|
|
856
|
+
}) {
|
|
857
|
+
this.id = props.id;
|
|
858
|
+
this.email = props.email;
|
|
859
|
+
this.name = props.name as string;
|
|
860
|
+
this.age = props.age as number;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Deserializes input to an instance of this class.
|
|
865
|
+
* Automatically detects whether input is a JSON string or object.
|
|
866
|
+
* @param input - JSON string or object to deserialize
|
|
867
|
+
* @param opts - Optional deserialization options
|
|
868
|
+
* @returns Result containing the deserialized instance or validation errors
|
|
869
|
+
*/
|
|
870
|
+
static deserialize(
|
|
871
|
+
input: unknown,
|
|
872
|
+
opts?: DeserializeOptions
|
|
873
|
+
): Result<
|
|
874
|
+
User,
|
|
875
|
+
Array<{
|
|
876
|
+
field: string;
|
|
877
|
+
message: string;
|
|
878
|
+
}>
|
|
879
|
+
> {
|
|
880
|
+
try {
|
|
881
|
+
// Auto-detect: if string, parse as JSON first
|
|
882
|
+
const data = typeof input === 'string' ? JSON.parse(input) : input;
|
|
883
|
+
|
|
884
|
+
const ctx = DeserializeContext.create();
|
|
885
|
+
const resultOrRef = User.deserializeWithContext(data, ctx);
|
|
886
|
+
if (PendingRef.is(resultOrRef)) {
|
|
887
|
+
return Result.err([
|
|
888
|
+
{
|
|
889
|
+
field: '_root',
|
|
890
|
+
message: 'User.deserialize: root cannot be a forward reference'
|
|
891
|
+
}
|
|
892
|
+
]);
|
|
893
|
+
}
|
|
894
|
+
ctx.applyPatches();
|
|
895
|
+
if (opts?.freeze) {
|
|
896
|
+
ctx.freezeAll();
|
|
897
|
+
}
|
|
898
|
+
return Result.ok(resultOrRef);
|
|
899
|
+
} catch (e) {
|
|
900
|
+
if (e instanceof DeserializeError) {
|
|
901
|
+
return Result.err(e.errors);
|
|
902
|
+
}
|
|
903
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
904
|
+
return Result.err([
|
|
905
|
+
{
|
|
906
|
+
field: '_root',
|
|
907
|
+
message
|
|
908
|
+
}
|
|
909
|
+
]);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/** @internal */
|
|
914
|
+
static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
|
|
915
|
+
if (value?.__ref !== undefined) {
|
|
916
|
+
return ctx.getOrDefer(value.__ref);
|
|
917
|
+
}
|
|
918
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
919
|
+
throw new DeserializeError([
|
|
920
|
+
{
|
|
921
|
+
field: '_root',
|
|
922
|
+
message: 'User.deserializeWithContext: expected an object'
|
|
923
|
+
}
|
|
924
|
+
]);
|
|
925
|
+
}
|
|
926
|
+
const obj = value as Record<string, unknown>;
|
|
927
|
+
const errors: Array<{
|
|
928
|
+
field: string;
|
|
929
|
+
message: string;
|
|
930
|
+
}> = [];
|
|
931
|
+
const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
|
|
932
|
+
for (const key of Object.keys(obj)) {
|
|
933
|
+
if (!knownKeys.has(key)) {
|
|
934
|
+
errors.push({
|
|
935
|
+
field: key,
|
|
936
|
+
message: 'unknown field'
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (!('id' in obj)) {
|
|
941
|
+
errors.push({
|
|
942
|
+
field: 'id',
|
|
943
|
+
message: 'missing required field'
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
if (!('email' in obj)) {
|
|
947
|
+
errors.push({
|
|
948
|
+
field: 'email',
|
|
949
|
+
message: 'missing required field'
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
if (errors.length > 0) {
|
|
953
|
+
throw new DeserializeError(errors);
|
|
954
|
+
}
|
|
955
|
+
const instance = Object.create(User.prototype) as User;
|
|
956
|
+
if (obj.__id !== undefined) {
|
|
957
|
+
ctx.register(obj.__id as number, instance);
|
|
958
|
+
}
|
|
959
|
+
ctx.trackForFreeze(instance);
|
|
960
|
+
{
|
|
961
|
+
const __raw_id = obj['id'] as number;
|
|
962
|
+
instance.id = __raw_id;
|
|
963
|
+
}
|
|
964
|
+
{
|
|
965
|
+
const __raw_email = obj['email'] as string;
|
|
966
|
+
instance.email = __raw_email;
|
|
967
|
+
}
|
|
968
|
+
if ('name' in obj && obj['name'] !== undefined) {
|
|
969
|
+
const __raw_name = obj['name'] as string;
|
|
970
|
+
instance.name = __raw_name;
|
|
971
|
+
} else {
|
|
972
|
+
instance.name = "guest";
|
|
973
|
+
}
|
|
974
|
+
if ('age' in obj && obj['age'] !== undefined) {
|
|
975
|
+
const __raw_age = obj['age'] as number;
|
|
976
|
+
instance.age = __raw_age;
|
|
977
|
+
}
|
|
978
|
+
if (errors.length > 0) {
|
|
979
|
+
throw new DeserializeError(errors);
|
|
980
|
+
}
|
|
981
|
+
return instance;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
static validateField<K extends keyof User>(
|
|
985
|
+
field: K,
|
|
986
|
+
value: User[K]
|
|
987
|
+
): Array<{
|
|
988
|
+
field: string;
|
|
989
|
+
message: string;
|
|
990
|
+
}> {
|
|
991
|
+
return [];
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
static validateFields(partial: Partial<User>): Array<{
|
|
995
|
+
field: string;
|
|
996
|
+
message: string;
|
|
997
|
+
}> {
|
|
998
|
+
return [];
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
static hasShape(obj: unknown): boolean {
|
|
1002
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
const o = obj as Record<string, unknown>;
|
|
1006
|
+
return 'id' in o && 'email' in o;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
static is(obj: unknown): obj is User {
|
|
1010
|
+
if (obj instanceof User) {
|
|
1011
|
+
return true;
|
|
1012
|
+
}
|
|
1013
|
+
if (!User.hasShape(obj)) {
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
const result = User.deserialize(obj);
|
|
1017
|
+
return Result.isOk(result);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Usage:
|
|
1022
|
+
const result = User.deserialize('{"id":1,"email":"test@example.com"}');
|
|
1023
|
+
if (Result.isOk(result)) {
|
|
1024
|
+
const user = result.value;
|
|
1025
|
+
} else {
|
|
1026
|
+
console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
|
|
1027
|
+
}
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
Generated output:
|
|
1031
|
+
|
|
1032
|
+
```typescript
|
|
1033
|
+
import { DeserializeContext } from 'macroforge/serde';
|
|
1034
|
+
import { DeserializeError } from 'macroforge/serde';
|
|
1035
|
+
import type { DeserializeOptions } from 'macroforge/serde';
|
|
1036
|
+
import { PendingRef } from 'macroforge/serde';
|
|
1037
|
+
|
|
1038
|
+
/** @serde({ denyUnknownFields: true }) */
|
|
1039
|
+
class User {
|
|
1040
|
+
id: number;
|
|
1041
|
+
|
|
1042
|
+
email: string;
|
|
1043
|
+
|
|
1044
|
+
name: string;
|
|
1045
|
+
|
|
1046
|
+
age?: number;
|
|
1047
|
+
|
|
1048
|
+
constructor(props: {
|
|
1049
|
+
id: number;
|
|
1050
|
+
email: string;
|
|
1051
|
+
name?: string;
|
|
1052
|
+
age?: number;
|
|
1053
|
+
}) {
|
|
1054
|
+
this.id = props.id;
|
|
1055
|
+
this.email = props.email;
|
|
1056
|
+
this.name = props.name as string;
|
|
1057
|
+
this.age = props.age as number;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Deserializes input to an instance of this class.
|
|
1062
|
+
* Automatically detects whether input is a JSON string or object.
|
|
1063
|
+
* @param input - JSON string or object to deserialize
|
|
1064
|
+
* @param opts - Optional deserialization options
|
|
1065
|
+
* @returns Result containing the deserialized instance or validation errors
|
|
1066
|
+
*/
|
|
1067
|
+
static deserialize(
|
|
1068
|
+
input: unknown,
|
|
1069
|
+
opts?: DeserializeOptions
|
|
1070
|
+
): Result<
|
|
1071
|
+
User,
|
|
1072
|
+
Array<{
|
|
1073
|
+
field: string;
|
|
1074
|
+
message: string;
|
|
1075
|
+
}>
|
|
1076
|
+
> {
|
|
1077
|
+
try {
|
|
1078
|
+
// Auto-detect: if string, parse as JSON first
|
|
1079
|
+
const data = typeof input === 'string' ? JSON.parse(input) : input;
|
|
1080
|
+
|
|
1081
|
+
const ctx = DeserializeContext.create();
|
|
1082
|
+
const resultOrRef = User.deserializeWithContext(data, ctx);
|
|
1083
|
+
if (PendingRef.is(resultOrRef)) {
|
|
1084
|
+
return Result.err([
|
|
1085
|
+
{
|
|
1086
|
+
field: '_root',
|
|
1087
|
+
message: 'User.deserialize: root cannot be a forward reference'
|
|
1088
|
+
}
|
|
1089
|
+
]);
|
|
1090
|
+
}
|
|
1091
|
+
ctx.applyPatches();
|
|
1092
|
+
if (opts?.freeze) {
|
|
1093
|
+
ctx.freezeAll();
|
|
1094
|
+
}
|
|
1095
|
+
return Result.ok(resultOrRef);
|
|
1096
|
+
} catch (e) {
|
|
1097
|
+
if (e instanceof DeserializeError) {
|
|
1098
|
+
return Result.err(e.errors);
|
|
1099
|
+
}
|
|
1100
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1101
|
+
return Result.err([
|
|
1102
|
+
{
|
|
1103
|
+
field: '_root',
|
|
1104
|
+
message
|
|
1105
|
+
}
|
|
1106
|
+
]);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/** @internal */
|
|
1111
|
+
static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
|
|
1112
|
+
if (value?.__ref !== undefined) {
|
|
1113
|
+
return ctx.getOrDefer(value.__ref);
|
|
1114
|
+
}
|
|
1115
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
1116
|
+
throw new DeserializeError([
|
|
1117
|
+
{
|
|
1118
|
+
field: '_root',
|
|
1119
|
+
message: 'User.deserializeWithContext: expected an object'
|
|
1120
|
+
}
|
|
1121
|
+
]);
|
|
1122
|
+
}
|
|
1123
|
+
const obj = value as Record<string, unknown>;
|
|
1124
|
+
const errors: Array<{
|
|
1125
|
+
field: string;
|
|
1126
|
+
message: string;
|
|
1127
|
+
}> = [];
|
|
1128
|
+
const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
|
|
1129
|
+
for (const key of Object.keys(obj)) {
|
|
1130
|
+
if (!knownKeys.has(key)) {
|
|
1131
|
+
errors.push({
|
|
1132
|
+
field: key,
|
|
1133
|
+
message: 'unknown field'
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
if (!('id' in obj)) {
|
|
1138
|
+
errors.push({
|
|
1139
|
+
field: 'id',
|
|
1140
|
+
message: 'missing required field'
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
if (!('email' in obj)) {
|
|
1144
|
+
errors.push({
|
|
1145
|
+
field: 'email',
|
|
1146
|
+
message: 'missing required field'
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
if (errors.length > 0) {
|
|
1150
|
+
throw new DeserializeError(errors);
|
|
1151
|
+
}
|
|
1152
|
+
const instance = Object.create(User.prototype) as User;
|
|
1153
|
+
if (obj.__id !== undefined) {
|
|
1154
|
+
ctx.register(obj.__id as number, instance);
|
|
1155
|
+
}
|
|
1156
|
+
ctx.trackForFreeze(instance);
|
|
1157
|
+
{
|
|
1158
|
+
const __raw_id = obj['id'] as number;
|
|
1159
|
+
instance.id = __raw_id;
|
|
1160
|
+
}
|
|
1161
|
+
{
|
|
1162
|
+
const __raw_email = obj['email'] as string;
|
|
1163
|
+
instance.email = __raw_email;
|
|
1164
|
+
}
|
|
1165
|
+
if ('name' in obj && obj['name'] !== undefined) {
|
|
1166
|
+
const __raw_name = obj['name'] as string;
|
|
1167
|
+
instance.name = __raw_name;
|
|
1168
|
+
} else {
|
|
1169
|
+
instance.name = 'guest';
|
|
1170
|
+
}
|
|
1171
|
+
if ('age' in obj && obj['age'] !== undefined) {
|
|
1172
|
+
const __raw_age = obj['age'] as number;
|
|
1173
|
+
instance.age = __raw_age;
|
|
1174
|
+
}
|
|
1175
|
+
if (errors.length > 0) {
|
|
1176
|
+
throw new DeserializeError(errors);
|
|
1177
|
+
}
|
|
1178
|
+
return instance;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
static validateField<K extends keyof User>(
|
|
1182
|
+
field: K,
|
|
1183
|
+
value: User[K]
|
|
1184
|
+
): Array<{
|
|
1185
|
+
field: string;
|
|
1186
|
+
message: string;
|
|
1187
|
+
}> {
|
|
1188
|
+
return [];
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
static validateFields(partial: Partial<User>): Array<{
|
|
1192
|
+
field: string;
|
|
1193
|
+
message: string;
|
|
1194
|
+
}> {
|
|
1195
|
+
return [];
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
static hasShape(obj: unknown): boolean {
|
|
1199
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
const o = obj as Record<string, unknown>;
|
|
1203
|
+
return 'id' in o && 'email' in o;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
static is(obj: unknown): obj is User {
|
|
1207
|
+
if (obj instanceof User) {
|
|
1208
|
+
return true;
|
|
1209
|
+
}
|
|
1210
|
+
if (!User.hasShape(obj)) {
|
|
1211
|
+
return false;
|
|
1212
|
+
}
|
|
1213
|
+
const result = User.deserialize(obj);
|
|
1214
|
+
return Result.isOk(result);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Usage:
|
|
1219
|
+
const result = User.deserialize('{"id":1,"email":"test@example.com"}');
|
|
1220
|
+
if (Result.isOk(result)) {
|
|
1221
|
+
const user = result.value;
|
|
1222
|
+
} else {
|
|
1223
|
+
console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
|
|
1224
|
+
}
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
Generated output:
|
|
1228
|
+
|
|
1229
|
+
```typescript
|
|
1230
|
+
import { DeserializeContext } from 'macroforge/serde';
|
|
1231
|
+
import { DeserializeError } from 'macroforge/serde';
|
|
1232
|
+
import type { DeserializeOptions } from 'macroforge/serde';
|
|
1233
|
+
import { PendingRef } from 'macroforge/serde';
|
|
1234
|
+
|
|
1235
|
+
/** @serde({ denyUnknownFields: true }) */
|
|
1236
|
+
class User {
|
|
1237
|
+
id: number;
|
|
1238
|
+
|
|
1239
|
+
email: string;
|
|
1240
|
+
|
|
1241
|
+
name: string;
|
|
1242
|
+
|
|
1243
|
+
age?: number;
|
|
1244
|
+
|
|
1245
|
+
constructor(props: {
|
|
1246
|
+
id: number;
|
|
1247
|
+
email: string;
|
|
1248
|
+
name?: string;
|
|
1249
|
+
age?: number;
|
|
1250
|
+
}) {
|
|
1251
|
+
this.id = props.id;
|
|
1252
|
+
this.email = props.email;
|
|
1253
|
+
this.name = props.name as string;
|
|
1254
|
+
this.age = props.age as number;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* Deserializes input to an instance of this class.
|
|
1259
|
+
* Automatically detects whether input is a JSON string or object.
|
|
1260
|
+
* @param input - JSON string or object to deserialize
|
|
1261
|
+
* @param opts - Optional deserialization options
|
|
1262
|
+
* @returns Result containing the deserialized instance or validation errors
|
|
1263
|
+
*/
|
|
1264
|
+
static deserialize(
|
|
1265
|
+
input: unknown,
|
|
1266
|
+
opts?: DeserializeOptions
|
|
1267
|
+
): Result<
|
|
1268
|
+
User,
|
|
1269
|
+
Array<{
|
|
1270
|
+
field: string;
|
|
1271
|
+
message: string;
|
|
1272
|
+
}>
|
|
1273
|
+
> {
|
|
1274
|
+
try {
|
|
1275
|
+
// Auto-detect: if string, parse as JSON first
|
|
1276
|
+
const data = typeof input === 'string' ? JSON.parse(input) : input;
|
|
1277
|
+
|
|
1278
|
+
const ctx = DeserializeContext.create();
|
|
1279
|
+
const resultOrRef = User.deserializeWithContext(data, ctx);
|
|
1280
|
+
if (PendingRef.is(resultOrRef)) {
|
|
1281
|
+
return Result.err([
|
|
1282
|
+
{
|
|
1283
|
+
field: '_root',
|
|
1284
|
+
message: 'User.deserialize: root cannot be a forward reference'
|
|
1285
|
+
}
|
|
1286
|
+
]);
|
|
1287
|
+
}
|
|
1288
|
+
ctx.applyPatches();
|
|
1289
|
+
if (opts?.freeze) {
|
|
1290
|
+
ctx.freezeAll();
|
|
1291
|
+
}
|
|
1292
|
+
return Result.ok(resultOrRef);
|
|
1293
|
+
} catch (e) {
|
|
1294
|
+
if (e instanceof DeserializeError) {
|
|
1295
|
+
return Result.err(e.errors);
|
|
1296
|
+
}
|
|
1297
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1298
|
+
return Result.err([
|
|
1299
|
+
{
|
|
1300
|
+
field: '_root',
|
|
1301
|
+
message
|
|
1302
|
+
}
|
|
1303
|
+
]);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
/** @internal */
|
|
1308
|
+
static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
|
|
1309
|
+
if (value?.__ref !== undefined) {
|
|
1310
|
+
return ctx.getOrDefer(value.__ref);
|
|
1311
|
+
}
|
|
1312
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
1313
|
+
throw new DeserializeError([
|
|
1314
|
+
{
|
|
1315
|
+
field: '_root',
|
|
1316
|
+
message: 'User.deserializeWithContext: expected an object'
|
|
1317
|
+
}
|
|
1318
|
+
]);
|
|
1319
|
+
}
|
|
1320
|
+
const obj = value as Record<string, unknown>;
|
|
1321
|
+
const errors: Array<{
|
|
1322
|
+
field: string;
|
|
1323
|
+
message: string;
|
|
1324
|
+
}> = [];
|
|
1325
|
+
const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
|
|
1326
|
+
for (const key of Object.keys(obj)) {
|
|
1327
|
+
if (!knownKeys.has(key)) {
|
|
1328
|
+
errors.push({
|
|
1329
|
+
field: key,
|
|
1330
|
+
message: 'unknown field'
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (!('id' in obj)) {
|
|
1335
|
+
errors.push({
|
|
1336
|
+
field: 'id',
|
|
1337
|
+
message: 'missing required field'
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
if (!('email' in obj)) {
|
|
1341
|
+
errors.push({
|
|
1342
|
+
field: 'email',
|
|
1343
|
+
message: 'missing required field'
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
if (errors.length > 0) {
|
|
1347
|
+
throw new DeserializeError(errors);
|
|
1348
|
+
}
|
|
1349
|
+
const instance = Object.create(User.prototype) as User;
|
|
1350
|
+
if (obj.__id !== undefined) {
|
|
1351
|
+
ctx.register(obj.__id as number, instance);
|
|
1352
|
+
}
|
|
1353
|
+
ctx.trackForFreeze(instance);
|
|
1354
|
+
{
|
|
1355
|
+
const __raw_id = obj['id'] as number;
|
|
1356
|
+
instance.id = __raw_id;
|
|
1357
|
+
}
|
|
1358
|
+
{
|
|
1359
|
+
const __raw_email = obj['email'] as string;
|
|
1360
|
+
instance.email = __raw_email;
|
|
1361
|
+
}
|
|
1362
|
+
if ('name' in obj && obj['name'] !== undefined) {
|
|
1363
|
+
const __raw_name = obj['name'] as string;
|
|
1364
|
+
instance.name = __raw_name;
|
|
1365
|
+
} else {
|
|
1366
|
+
instance.name = 'guest';
|
|
1367
|
+
}
|
|
1368
|
+
if ('age' in obj && obj['age'] !== undefined) {
|
|
1369
|
+
const __raw_age = obj['age'] as number;
|
|
1370
|
+
instance.age = __raw_age;
|
|
1371
|
+
}
|
|
1372
|
+
if (errors.length > 0) {
|
|
1373
|
+
throw new DeserializeError(errors);
|
|
1374
|
+
}
|
|
1375
|
+
return instance;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
static validateField<K extends keyof User>(
|
|
1379
|
+
field: K,
|
|
1380
|
+
value: User[K]
|
|
1381
|
+
): Array<{
|
|
1382
|
+
field: string;
|
|
1383
|
+
message: string;
|
|
1384
|
+
}> {
|
|
1385
|
+
return [];
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
static validateFields(partial: Partial<User>): Array<{
|
|
1389
|
+
field: string;
|
|
1390
|
+
message: string;
|
|
1391
|
+
}> {
|
|
1392
|
+
return [];
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
static hasShape(obj: unknown): boolean {
|
|
1396
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
1397
|
+
return false;
|
|
1398
|
+
}
|
|
1399
|
+
const o = obj as Record<string, unknown>;
|
|
1400
|
+
return 'id' in o && 'email' in o;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
static is(obj: unknown): obj is User {
|
|
1404
|
+
if (obj instanceof User) {
|
|
1405
|
+
return true;
|
|
1406
|
+
}
|
|
1407
|
+
if (!User.hasShape(obj)) {
|
|
1408
|
+
return false;
|
|
1409
|
+
}
|
|
1410
|
+
const result = User.deserialize(obj);
|
|
1411
|
+
return Result.isOk(result);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// Usage:
|
|
1416
|
+
const result = User.deserialize('{"id":1,"email":"test@example.com"}');
|
|
1417
|
+
if (Result.isOk(result)) {
|
|
1418
|
+
const user = result.value;
|
|
1419
|
+
} else {
|
|
1420
|
+
console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
|
|
1421
|
+
}
|
|
1422
|
+
```
|
|
1423
|
+
|
|
1424
|
+
Generated output:
|
|
1425
|
+
|
|
1426
|
+
```typescript
|
|
1427
|
+
import { DeserializeContext } from 'macroforge/serde';
|
|
1428
|
+
import { DeserializeError } from 'macroforge/serde';
|
|
1429
|
+
import type { DeserializeOptions } from 'macroforge/serde';
|
|
1430
|
+
import { PendingRef } from 'macroforge/serde';
|
|
1431
|
+
|
|
1432
|
+
/** @serde({ denyUnknownFields: true }) */
|
|
1433
|
+
class User {
|
|
1434
|
+
id: number;
|
|
1435
|
+
|
|
1436
|
+
email: string;
|
|
1437
|
+
|
|
1438
|
+
name: string;
|
|
1439
|
+
|
|
1440
|
+
age?: number;
|
|
1441
|
+
|
|
1442
|
+
constructor(props: {
|
|
1443
|
+
id: number;
|
|
1444
|
+
email: string;
|
|
1445
|
+
name?: string;
|
|
1446
|
+
age?: number;
|
|
1447
|
+
}) {
|
|
1448
|
+
this.id = props.id;
|
|
1449
|
+
this.email = props.email;
|
|
1450
|
+
this.name = props.name as string;
|
|
1451
|
+
this.age = props.age as number;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/**
|
|
1455
|
+
* Deserializes input to an instance of this class.
|
|
1456
|
+
* Automatically detects whether input is a JSON string or object.
|
|
1457
|
+
* @param input - JSON string or object to deserialize
|
|
1458
|
+
* @param opts - Optional deserialization options
|
|
1459
|
+
* @returns Result containing the deserialized instance or validation errors
|
|
1460
|
+
*/
|
|
1461
|
+
static deserialize(
|
|
1462
|
+
input: unknown,
|
|
1463
|
+
opts?: DeserializeOptions
|
|
1464
|
+
): Result<
|
|
1465
|
+
User,
|
|
1466
|
+
Array<{
|
|
1467
|
+
field: string;
|
|
1468
|
+
message: string;
|
|
1469
|
+
}>
|
|
1470
|
+
> {
|
|
1471
|
+
try {
|
|
1472
|
+
// Auto-detect: if string, parse as JSON first
|
|
1473
|
+
const data = typeof input === 'string' ? JSON.parse(input) : input;
|
|
1474
|
+
|
|
1475
|
+
const ctx = DeserializeContext.create();
|
|
1476
|
+
const resultOrRef = User.deserializeWithContext(data, ctx);
|
|
1477
|
+
if (PendingRef.is(resultOrRef)) {
|
|
1478
|
+
return Result.err([
|
|
1479
|
+
{
|
|
1480
|
+
field: '_root',
|
|
1481
|
+
message: 'User.deserialize: root cannot be a forward reference'
|
|
1482
|
+
}
|
|
1483
|
+
]);
|
|
1484
|
+
}
|
|
1485
|
+
ctx.applyPatches();
|
|
1486
|
+
if (opts?.freeze) {
|
|
1487
|
+
ctx.freezeAll();
|
|
1488
|
+
}
|
|
1489
|
+
return Result.ok(resultOrRef);
|
|
1490
|
+
} catch (e) {
|
|
1491
|
+
if (e instanceof DeserializeError) {
|
|
1492
|
+
return Result.err(e.errors);
|
|
1493
|
+
}
|
|
1494
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1495
|
+
return Result.err([
|
|
1496
|
+
{
|
|
1497
|
+
field: '_root',
|
|
1498
|
+
message
|
|
1499
|
+
}
|
|
1500
|
+
]);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
/** @internal */
|
|
1505
|
+
static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
|
|
1506
|
+
if (value?.__ref !== undefined) {
|
|
1507
|
+
return ctx.getOrDefer(value.__ref);
|
|
1508
|
+
}
|
|
1509
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
1510
|
+
throw new DeserializeError([
|
|
1511
|
+
{
|
|
1512
|
+
field: '_root',
|
|
1513
|
+
message: 'User.deserializeWithContext: expected an object'
|
|
1514
|
+
}
|
|
1515
|
+
]);
|
|
1516
|
+
}
|
|
1517
|
+
const obj = value as Record<string, unknown>;
|
|
1518
|
+
const errors: Array<{
|
|
1519
|
+
field: string;
|
|
1520
|
+
message: string;
|
|
1521
|
+
}> = [];
|
|
1522
|
+
const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
|
|
1523
|
+
for (const key of Object.keys(obj)) {
|
|
1524
|
+
if (!knownKeys.has(key)) {
|
|
1525
|
+
errors.push({
|
|
1526
|
+
field: key,
|
|
1527
|
+
message: 'unknown field'
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (!('id' in obj)) {
|
|
1532
|
+
errors.push({
|
|
1533
|
+
field: 'id',
|
|
1534
|
+
message: 'missing required field'
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
if (!('email' in obj)) {
|
|
1538
|
+
errors.push({
|
|
1539
|
+
field: 'email',
|
|
1540
|
+
message: 'missing required field'
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
if (errors.length > 0) {
|
|
1544
|
+
throw new DeserializeError(errors);
|
|
1545
|
+
}
|
|
1546
|
+
const instance = Object.create(User.prototype) as User;
|
|
1547
|
+
if (obj.__id !== undefined) {
|
|
1548
|
+
ctx.register(obj.__id as number, instance);
|
|
1549
|
+
}
|
|
1550
|
+
ctx.trackForFreeze(instance);
|
|
1551
|
+
{
|
|
1552
|
+
const __raw_id = obj['id'] as number;
|
|
1553
|
+
instance.id = __raw_id;
|
|
1554
|
+
}
|
|
1555
|
+
{
|
|
1556
|
+
const __raw_email = obj['email'] as string;
|
|
1557
|
+
instance.email = __raw_email;
|
|
1558
|
+
}
|
|
1559
|
+
if ('name' in obj && obj['name'] !== undefined) {
|
|
1560
|
+
const __raw_name = obj['name'] as string;
|
|
1561
|
+
instance.name = __raw_name;
|
|
1562
|
+
} else {
|
|
1563
|
+
instance.name = 'guest';
|
|
1564
|
+
}
|
|
1565
|
+
if ('age' in obj && obj['age'] !== undefined) {
|
|
1566
|
+
const __raw_age = obj['age'] as number;
|
|
1567
|
+
instance.age = __raw_age;
|
|
1568
|
+
}
|
|
1569
|
+
if (errors.length > 0) {
|
|
1570
|
+
throw new DeserializeError(errors);
|
|
1571
|
+
}
|
|
1572
|
+
return instance;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
static validateField<K extends keyof User>(
|
|
1576
|
+
field: K,
|
|
1577
|
+
value: User[K]
|
|
1578
|
+
): Array<{
|
|
1579
|
+
field: string;
|
|
1580
|
+
message: string;
|
|
1581
|
+
}> {
|
|
1582
|
+
return [];
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
static validateFields(partial: Partial<User>): Array<{
|
|
1586
|
+
field: string;
|
|
1587
|
+
message: string;
|
|
1588
|
+
}> {
|
|
1589
|
+
return [];
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
static hasShape(obj: unknown): boolean {
|
|
1593
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
1594
|
+
return false;
|
|
1595
|
+
}
|
|
1596
|
+
const o = obj as Record<string, unknown>;
|
|
1597
|
+
return 'id' in o && 'email' in o;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
static is(obj: unknown): obj is User {
|
|
1601
|
+
if (obj instanceof User) {
|
|
1602
|
+
return true;
|
|
1603
|
+
}
|
|
1604
|
+
if (!User.hasShape(obj)) {
|
|
1605
|
+
return false;
|
|
1606
|
+
}
|
|
1607
|
+
const result = User.deserialize(obj);
|
|
1608
|
+
return Result.isOk(result);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// Usage:
|
|
1613
|
+
const result = User.deserialize('{"id":1,"email":"test@example.com"}');
|
|
1614
|
+
if (Result.isOk(result)) {
|
|
1615
|
+
const user = result.value;
|
|
1616
|
+
} else {
|
|
1617
|
+
console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
|
|
1618
|
+
}
|
|
1619
|
+
```
|
|
1620
|
+
|
|
1621
|
+
## Required Imports
|
|
1622
|
+
|
|
1623
|
+
The generated code automatically imports:
|
|
1624
|
+
- `Result` from `macroforge/utils`
|
|
1625
|
+
- `DeserializeContext`, `DeserializeError`, `PendingRef` from `macroforge/serde`
|