@macroforge/mcp-server 0.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/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/docs-loader.d.ts +30 -0
- package/dist/tools/docs-loader.d.ts.map +1 -0
- package/dist/tools/docs-loader.js +112 -0
- package/dist/tools/docs-loader.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +348 -0
- package/dist/tools/index.js.map +1 -0
- package/docs/api/api-overview.md +75 -0
- package/docs/api/expand-sync.md +121 -0
- package/docs/api/native-plugin.md +106 -0
- package/docs/api/position-mapper.md +127 -0
- package/docs/api/transform-sync.md +98 -0
- package/docs/builtin-macros/clone.md +180 -0
- package/docs/builtin-macros/debug.md +222 -0
- package/docs/builtin-macros/default.md +192 -0
- package/docs/builtin-macros/deserialize.md +662 -0
- package/docs/builtin-macros/hash.md +205 -0
- package/docs/builtin-macros/macros-overview.md +169 -0
- package/docs/builtin-macros/ord.md +258 -0
- package/docs/builtin-macros/partial-eq.md +306 -0
- package/docs/builtin-macros/partial-ord.md +268 -0
- package/docs/builtin-macros/serialize.md +321 -0
- package/docs/concepts/architecture.md +139 -0
- package/docs/concepts/derive-system.md +173 -0
- package/docs/concepts/how-macros-work.md +124 -0
- package/docs/custom-macros/custom-overview.md +84 -0
- package/docs/custom-macros/rust-setup.md +146 -0
- package/docs/custom-macros/ts-macro-derive.md +307 -0
- package/docs/custom-macros/ts-quote.md +696 -0
- package/docs/getting-started/first-macro.md +120 -0
- package/docs/getting-started/installation.md +110 -0
- package/docs/integration/cli.md +207 -0
- package/docs/integration/configuration.md +116 -0
- package/docs/integration/integration-overview.md +51 -0
- package/docs/integration/typescript-plugin.md +96 -0
- package/docs/integration/vite-plugin.md +126 -0
- package/docs/language-servers/ls-overview.md +47 -0
- package/docs/language-servers/svelte-ls.md +80 -0
- package/docs/language-servers/zed-extensions.md +84 -0
- package/docs/sections.json +258 -0
- package/package.json +48 -0
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
# Deserialize
|
|
2
|
+
|
|
3
|
+
*The `Deserialize` macro generates a static `fromJSON()` method that parses JSON data into your class with runtime validation and automatic type conversion.*
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
/** @derive(Deserialize) */
|
|
9
|
+
class User {
|
|
10
|
+
name: string;
|
|
11
|
+
age: number;
|
|
12
|
+
createdAt: Date;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const json = '{"name":"Alice","age":30,"createdAt":"2024-01-15T10:30:00.000Z"}';
|
|
16
|
+
const user = User.fromJSON(JSON.parse(json));
|
|
17
|
+
|
|
18
|
+
console.log(user.name); // "Alice"
|
|
19
|
+
console.log(user.age); // 30
|
|
20
|
+
console.log(user.createdAt instanceof Date); // true
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Generated Code
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
static fromJSON(data: unknown): User {
|
|
27
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
28
|
+
throw new Error("User.fromJSON: expected an object, got " + typeof data);
|
|
29
|
+
}
|
|
30
|
+
const obj = data as Record<string, unknown>;
|
|
31
|
+
|
|
32
|
+
if (!("name" in obj)) {
|
|
33
|
+
throw new Error("User.fromJSON: missing required field \\"name\\"");
|
|
34
|
+
}
|
|
35
|
+
if (!("age" in obj)) {
|
|
36
|
+
throw new Error("User.fromJSON: missing required field \\"age\\"");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const instance = new User();
|
|
40
|
+
instance.name = obj["name"] as string;
|
|
41
|
+
instance.age = obj["age"] as number;
|
|
42
|
+
instance.createdAt = new Date(obj["createdAt"] as string);
|
|
43
|
+
return instance;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Runtime Validation
|
|
48
|
+
|
|
49
|
+
Deserialize validates the input data and throws descriptive errors:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
/** @derive(Deserialize) */
|
|
53
|
+
class User {
|
|
54
|
+
name: string;
|
|
55
|
+
email: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Missing required field
|
|
59
|
+
User.fromJSON({ name: "Alice" });
|
|
60
|
+
// Error: User.fromJSON: missing required field "email"
|
|
61
|
+
|
|
62
|
+
// Wrong type
|
|
63
|
+
User.fromJSON("not an object");
|
|
64
|
+
// Error: User.fromJSON: expected an object, got string
|
|
65
|
+
|
|
66
|
+
// Array instead of object
|
|
67
|
+
User.fromJSON([1, 2, 3]);
|
|
68
|
+
// Error: User.fromJSON: expected an object, got array
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Automatic Type Conversion
|
|
72
|
+
|
|
73
|
+
Deserialize automatically converts JSON types to their TypeScript equivalents:
|
|
74
|
+
|
|
75
|
+
| string/number/boolean
|
|
76
|
+
| `string`/`number`/`boolean`
|
|
77
|
+
| Direct assignment
|
|
78
|
+
|
|
79
|
+
| ISO string
|
|
80
|
+
| `Date`
|
|
81
|
+
| `new Date(string)`
|
|
82
|
+
|
|
83
|
+
| array
|
|
84
|
+
| `T[]`
|
|
85
|
+
| Maps items with auto-detection
|
|
86
|
+
|
|
87
|
+
| object
|
|
88
|
+
| `Map<K, V>`
|
|
89
|
+
| `new Map(Object.entries())`
|
|
90
|
+
|
|
91
|
+
| array
|
|
92
|
+
| `Set<T>`
|
|
93
|
+
| `new Set(array)`
|
|
94
|
+
|
|
95
|
+
| object
|
|
96
|
+
| Nested class
|
|
97
|
+
| Calls `fromJSON()` if available
|
|
98
|
+
|
|
99
|
+
## Serde Options
|
|
100
|
+
|
|
101
|
+
Use the `@serde` decorator to customize deserialization:
|
|
102
|
+
|
|
103
|
+
### Renaming Fields
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
/** @derive(Deserialize) */
|
|
107
|
+
class User {
|
|
108
|
+
/** @serde({ rename: "user_id" }) */
|
|
109
|
+
id: string;
|
|
110
|
+
|
|
111
|
+
/** @serde({ rename: "full_name" }) */
|
|
112
|
+
name: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const user = User.fromJSON({ user_id: "123", full_name: "Alice" });
|
|
116
|
+
console.log(user.id); // "123"
|
|
117
|
+
console.log(user.name); // "Alice"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Default Values
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
/** @derive(Deserialize) */
|
|
124
|
+
class Config {
|
|
125
|
+
host: string;
|
|
126
|
+
|
|
127
|
+
/** @serde({ default: "3000" }) */
|
|
128
|
+
port: string;
|
|
129
|
+
|
|
130
|
+
/** @serde({ default: "false" }) */
|
|
131
|
+
debug: boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const config = Config.fromJSON({ host: "localhost" });
|
|
135
|
+
console.log(config.port); // "3000"
|
|
136
|
+
console.log(config.debug); // false
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Skipping Fields
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
/** @derive(Deserialize) */
|
|
143
|
+
class User {
|
|
144
|
+
name: string;
|
|
145
|
+
email: string;
|
|
146
|
+
|
|
147
|
+
/** @serde({ skip: true }) */
|
|
148
|
+
cachedData: unknown;
|
|
149
|
+
|
|
150
|
+
/** @serde({ skip_deserializing: true }) */
|
|
151
|
+
computedField: string;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
<Alert type="tip" title="skip vs skip_deserializing">
|
|
156
|
+
Use `skip: true` to exclude from both serialization and deserialization.
|
|
157
|
+
Use `skip_deserializing: true` to only skip during deserialization.
|
|
158
|
+
</Alert>
|
|
159
|
+
|
|
160
|
+
### Deny Unknown Fields
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
/** @derive(Deserialize) */
|
|
164
|
+
/** @serde({ deny_unknown_fields: true }) */
|
|
165
|
+
class StrictUser {
|
|
166
|
+
name: string;
|
|
167
|
+
email: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// This will throw an error
|
|
171
|
+
StrictUser.fromJSON({ name: "Alice", email: "a@b.com", extra: "field" });
|
|
172
|
+
// Error: StrictUser.fromJSON: unknown field "extra"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Flatten Nested Objects
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
/** @derive(Deserialize) */
|
|
179
|
+
class Address {
|
|
180
|
+
city: string;
|
|
181
|
+
zip: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** @derive(Deserialize) */
|
|
185
|
+
class User {
|
|
186
|
+
name: string;
|
|
187
|
+
|
|
188
|
+
/** @serde({ flatten: true }) */
|
|
189
|
+
address: Address;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Flat JSON structure
|
|
193
|
+
const user = User.fromJSON({
|
|
194
|
+
name: "Alice",
|
|
195
|
+
city: "NYC",
|
|
196
|
+
zip: "10001"
|
|
197
|
+
});
|
|
198
|
+
console.log(user.address.city); // "NYC"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Field Validation
|
|
202
|
+
|
|
203
|
+
Use the `validate` option to add runtime validation to fields. Validation errors are collected and returned as `Result.err(string[])`.
|
|
204
|
+
|
|
205
|
+
### Basic Validation
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
/** @derive(Deserialize) */
|
|
209
|
+
class User {
|
|
210
|
+
/** @serde({ validate: ["email"] }) */
|
|
211
|
+
email: string;
|
|
212
|
+
|
|
213
|
+
/** @serde({ validate: ["minLength(2)", "maxLength(50)"] }) */
|
|
214
|
+
name: string;
|
|
215
|
+
|
|
216
|
+
/** @serde({ validate: ["positive", "int"] }) */
|
|
217
|
+
age: number;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const result = User.fromJSON({ email: "invalid", name: "A", age: -5 });
|
|
221
|
+
if (result.isErr()) {
|
|
222
|
+
console.log(result.unwrapErr());
|
|
223
|
+
// [
|
|
224
|
+
// 'User.fromJSON: field "email" must be a valid email',
|
|
225
|
+
// 'User.fromJSON: field "name" must have at least 2 characters',
|
|
226
|
+
// 'User.fromJSON: field "age" must be positive',
|
|
227
|
+
// ]
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Custom Error Messages
|
|
232
|
+
|
|
233
|
+
Use the object form to provide custom error messages:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
/** @derive(Deserialize) */
|
|
237
|
+
class Product {
|
|
238
|
+
/** @serde({ validate: [
|
|
239
|
+
{ validate: "nonEmpty", message: "Product name is required" },
|
|
240
|
+
{ validate: "maxLength(100)", message: "Name too long (max 100 chars)" }
|
|
241
|
+
] }) */
|
|
242
|
+
name: string;
|
|
243
|
+
|
|
244
|
+
/** @serde({ validate: [
|
|
245
|
+
{ validate: "positive", message: "Price must be greater than zero" }
|
|
246
|
+
] }) */
|
|
247
|
+
price: number;
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Custom Validator Functions
|
|
252
|
+
|
|
253
|
+
Use `custom(functionName)` to call your own validation function:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
function isValidSKU(value: string): boolean {
|
|
257
|
+
return /^[A-Z]{3}-\\d{4}$/.test(value);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** @derive(Deserialize) */
|
|
261
|
+
class Product {
|
|
262
|
+
/** @serde({ validate: [
|
|
263
|
+
{ validate: "custom(isValidSKU)", message: "Invalid SKU format (expected XXX-0000)" }
|
|
264
|
+
] }) */
|
|
265
|
+
sku: string;
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Available Validators
|
|
270
|
+
|
|
271
|
+
#### String Validators
|
|
272
|
+
|
|
273
|
+
| `email`
|
|
274
|
+
| Must be a valid email address
|
|
275
|
+
|
|
276
|
+
| `url`
|
|
277
|
+
| Must be a valid URL
|
|
278
|
+
|
|
279
|
+
| `uuid`
|
|
280
|
+
| Must be a valid UUID
|
|
281
|
+
|
|
282
|
+
| `nonEmpty`
|
|
283
|
+
| Must not be empty string
|
|
284
|
+
|
|
285
|
+
| `trimmed`
|
|
286
|
+
| Must have no leading/trailing whitespace
|
|
287
|
+
|
|
288
|
+
| `lowercase`
|
|
289
|
+
| Must be all lowercase
|
|
290
|
+
|
|
291
|
+
| `uppercase`
|
|
292
|
+
| Must be all uppercase
|
|
293
|
+
|
|
294
|
+
| `capitalized`
|
|
295
|
+
| First character must be uppercase
|
|
296
|
+
|
|
297
|
+
| `uncapitalized`
|
|
298
|
+
| First character must be lowercase
|
|
299
|
+
|
|
300
|
+
| `minLength(n)`
|
|
301
|
+
| Must have at least n characters
|
|
302
|
+
|
|
303
|
+
| `maxLength(n)`
|
|
304
|
+
| Must have at most n characters
|
|
305
|
+
|
|
306
|
+
| `length(n)`
|
|
307
|
+
| Must have exactly n characters
|
|
308
|
+
|
|
309
|
+
| `length(min, max)`
|
|
310
|
+
| Must have between min and max characters
|
|
311
|
+
|
|
312
|
+
| `pattern("regex")`
|
|
313
|
+
| Must match the regular expression
|
|
314
|
+
|
|
315
|
+
| `startsWith("prefix")`
|
|
316
|
+
| Must start with the given prefix
|
|
317
|
+
|
|
318
|
+
| `endsWith("suffix")`
|
|
319
|
+
| Must end with the given suffix
|
|
320
|
+
|
|
321
|
+
| `includes("substring")`
|
|
322
|
+
| Must contain the substring
|
|
323
|
+
|
|
324
|
+
#### Number Validators
|
|
325
|
+
|
|
326
|
+
| `positive`
|
|
327
|
+
| Must be greater than 0
|
|
328
|
+
|
|
329
|
+
| `negative`
|
|
330
|
+
| Must be less than 0
|
|
331
|
+
|
|
332
|
+
| `nonNegative`
|
|
333
|
+
| Must be 0 or greater
|
|
334
|
+
|
|
335
|
+
| `nonPositive`
|
|
336
|
+
| Must be 0 or less
|
|
337
|
+
|
|
338
|
+
| `int`
|
|
339
|
+
| Must be an integer
|
|
340
|
+
|
|
341
|
+
| `finite`
|
|
342
|
+
| Must be finite (not Infinity)
|
|
343
|
+
|
|
344
|
+
| `nonNaN`
|
|
345
|
+
| Must not be NaN
|
|
346
|
+
|
|
347
|
+
| `uint8`
|
|
348
|
+
| Must be integer 0-255
|
|
349
|
+
|
|
350
|
+
| `greaterThan(n)`
|
|
351
|
+
| Must be greater than n
|
|
352
|
+
|
|
353
|
+
| `greaterThanOrEqualTo(n)`
|
|
354
|
+
| Must be greater than or equal to n
|
|
355
|
+
|
|
356
|
+
| `lessThan(n)`
|
|
357
|
+
| Must be less than n
|
|
358
|
+
|
|
359
|
+
| `lessThanOrEqualTo(n)`
|
|
360
|
+
| Must be less than or equal to n
|
|
361
|
+
|
|
362
|
+
| `between(min, max)`
|
|
363
|
+
| Must be between min and max (inclusive)
|
|
364
|
+
|
|
365
|
+
| `multipleOf(n)`
|
|
366
|
+
| Must be a multiple of n
|
|
367
|
+
|
|
368
|
+
#### Array Validators
|
|
369
|
+
|
|
370
|
+
| `minItems(n)`
|
|
371
|
+
| Must have at least n items
|
|
372
|
+
|
|
373
|
+
| `maxItems(n)`
|
|
374
|
+
| Must have at most n items
|
|
375
|
+
|
|
376
|
+
| `itemsCount(n)`
|
|
377
|
+
| Must have exactly n items
|
|
378
|
+
|
|
379
|
+
#### Date Validators
|
|
380
|
+
|
|
381
|
+
| `validDate`
|
|
382
|
+
| Must be a valid date (not Invalid Date)
|
|
383
|
+
|
|
384
|
+
| `greaterThanDate("ISO")`
|
|
385
|
+
| Must be after the given date
|
|
386
|
+
|
|
387
|
+
| `greaterThanOrEqualToDate("ISO")`
|
|
388
|
+
| Must be on or after the given date
|
|
389
|
+
|
|
390
|
+
| `lessThanDate("ISO")`
|
|
391
|
+
| Must be before the given date
|
|
392
|
+
|
|
393
|
+
| `lessThanOrEqualToDate("ISO")`
|
|
394
|
+
| Must be on or before the given date
|
|
395
|
+
|
|
396
|
+
| `betweenDate("ISO1", "ISO2")`
|
|
397
|
+
| Must be between the two dates
|
|
398
|
+
|
|
399
|
+
#### BigInt Validators
|
|
400
|
+
|
|
401
|
+
| `positiveBigInt`
|
|
402
|
+
| Must be greater than 0n
|
|
403
|
+
|
|
404
|
+
| `negativeBigInt`
|
|
405
|
+
| Must be less than 0n
|
|
406
|
+
|
|
407
|
+
| `nonNegativeBigInt`
|
|
408
|
+
| Must be 0n or greater
|
|
409
|
+
|
|
410
|
+
| `nonPositiveBigInt`
|
|
411
|
+
| Must be 0n or less
|
|
412
|
+
|
|
413
|
+
| `greaterThanBigInt(n)`
|
|
414
|
+
| Must be greater than BigInt(n)
|
|
415
|
+
|
|
416
|
+
| `lessThanBigInt(n)`
|
|
417
|
+
| Must be less than BigInt(n)
|
|
418
|
+
|
|
419
|
+
| `betweenBigInt(min, max)`
|
|
420
|
+
| Must be between BigInt(min) and BigInt(max)
|
|
421
|
+
|
|
422
|
+
#### Custom Validators
|
|
423
|
+
|
|
424
|
+
| `custom(fnName)`
|
|
425
|
+
| Calls fnName(value), fails if it returns false
|
|
426
|
+
|
|
427
|
+
## All Options
|
|
428
|
+
|
|
429
|
+
### Container Options (on class/interface)
|
|
430
|
+
|
|
431
|
+
| `rename_all`
|
|
432
|
+
| `string`
|
|
433
|
+
| Apply naming convention to all fields
|
|
434
|
+
|
|
435
|
+
| `deny_unknown_fields`
|
|
436
|
+
| `boolean`
|
|
437
|
+
| Throw error if JSON has unknown keys
|
|
438
|
+
|
|
439
|
+
### Field Options (on properties)
|
|
440
|
+
|
|
441
|
+
| `rename`
|
|
442
|
+
| `string`
|
|
443
|
+
| Use a different JSON key
|
|
444
|
+
|
|
445
|
+
| `skip`
|
|
446
|
+
| `boolean`
|
|
447
|
+
| Exclude from serialization and deserialization
|
|
448
|
+
|
|
449
|
+
| `skip_deserializing`
|
|
450
|
+
| `boolean`
|
|
451
|
+
| Exclude from deserialization only
|
|
452
|
+
|
|
453
|
+
| `default`
|
|
454
|
+
| `boolean`
|
|
455
|
+
| Use TypeScript default if missing
|
|
456
|
+
|
|
457
|
+
| `default: "expr"`
|
|
458
|
+
| `string`
|
|
459
|
+
| Custom default expression
|
|
460
|
+
|
|
461
|
+
| `flatten`
|
|
462
|
+
| `boolean`
|
|
463
|
+
| Merge nested object fields from parent
|
|
464
|
+
|
|
465
|
+
| `validate`
|
|
466
|
+
| `string[] | object[]`
|
|
467
|
+
| Array of validators to run during deserialization
|
|
468
|
+
|
|
469
|
+
## Interface Support
|
|
470
|
+
|
|
471
|
+
Deserialize also works with interfaces. For interfaces, a namespace is generated with `is` (type guard) and `fromJSON` functions:
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
/** @derive(Deserialize) */
|
|
475
|
+
interface ApiResponse {
|
|
476
|
+
status: number;
|
|
477
|
+
message: string;
|
|
478
|
+
timestamp: Date;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Generated:
|
|
482
|
+
// export namespace ApiResponse {
|
|
483
|
+
// export function is(data: unknown): data is ApiResponse {
|
|
484
|
+
// if (typeof data !== "object" || data === null) return false;
|
|
485
|
+
// const obj = data as Record<string, unknown>;
|
|
486
|
+
// if (typeof obj["status"] !== "number") return false;
|
|
487
|
+
// if (typeof obj["message"] !== "string") return false;
|
|
488
|
+
// // ... additional checks
|
|
489
|
+
// return true;
|
|
490
|
+
// }
|
|
491
|
+
//
|
|
492
|
+
// export function fromJSON(data: unknown): ApiResponse {
|
|
493
|
+
// if (!is(data)) {
|
|
494
|
+
// throw new Error("ApiResponse.fromJSON: validation failed");
|
|
495
|
+
// }
|
|
496
|
+
// return {
|
|
497
|
+
// ...data,
|
|
498
|
+
// timestamp: new Date(data.timestamp)
|
|
499
|
+
// };
|
|
500
|
+
// }
|
|
501
|
+
// }
|
|
502
|
+
|
|
503
|
+
const json = { status: 200, message: "OK", timestamp: "2024-01-15T10:30:00.000Z" };
|
|
504
|
+
|
|
505
|
+
// Type guard
|
|
506
|
+
if (ApiResponse.is(json)) {
|
|
507
|
+
console.log(json.status); // TypeScript knows this is ApiResponse
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Deserialize with validation
|
|
511
|
+
const response = ApiResponse.fromJSON(json);
|
|
512
|
+
console.log(response.timestamp instanceof Date); // true
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
## Enum Support
|
|
516
|
+
|
|
517
|
+
Deserialize also works with enums. The `fromJSON` function validates that the input matches one of the enum values:
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
/** @derive(Deserialize) */
|
|
521
|
+
enum Status {
|
|
522
|
+
Active = "active",
|
|
523
|
+
Inactive = "inactive",
|
|
524
|
+
Pending = "pending",
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Generated:
|
|
528
|
+
// export namespace Status {
|
|
529
|
+
// export function fromJSON(data: unknown): Status {
|
|
530
|
+
// for (const key of Object.keys(Status)) {
|
|
531
|
+
// if (Status[key as keyof typeof Status] === data) {
|
|
532
|
+
// return data as Status;
|
|
533
|
+
// }
|
|
534
|
+
// }
|
|
535
|
+
// throw new Error(\`Invalid Status value: \${data}\`);
|
|
536
|
+
// }
|
|
537
|
+
// }
|
|
538
|
+
|
|
539
|
+
const status = Status.fromJSON("active");
|
|
540
|
+
console.log(status); // Status.Active
|
|
541
|
+
|
|
542
|
+
// Invalid values throw an error
|
|
543
|
+
try {
|
|
544
|
+
Status.fromJSON("invalid");
|
|
545
|
+
} catch (e) {
|
|
546
|
+
console.log(e.message); // "Invalid Status value: invalid"
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Works with numeric enums too:
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
/** @derive(Deserialize) */
|
|
554
|
+
enum Priority {
|
|
555
|
+
Low = 1,
|
|
556
|
+
Medium = 2,
|
|
557
|
+
High = 3,
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const priority = Priority.fromJSON(3);
|
|
561
|
+
console.log(priority); // Priority.High
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
## Type Alias Support
|
|
565
|
+
|
|
566
|
+
Deserialize works with type aliases. For object types, validation and type conversion is applied:
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
/** @derive(Deserialize) */
|
|
570
|
+
type UserProfile = {
|
|
571
|
+
id: string;
|
|
572
|
+
name: string;
|
|
573
|
+
createdAt: Date;
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// Generated:
|
|
577
|
+
// export namespace UserProfile {
|
|
578
|
+
// export function fromJSON(data: unknown): UserProfile {
|
|
579
|
+
// if (typeof data !== "object" || data === null) {
|
|
580
|
+
// throw new Error("UserProfile.fromJSON: expected object");
|
|
581
|
+
// }
|
|
582
|
+
// const obj = data as Record<string, unknown>;
|
|
583
|
+
// return {
|
|
584
|
+
// id: obj["id"] as string,
|
|
585
|
+
// name: obj["name"] as string,
|
|
586
|
+
// createdAt: new Date(obj["createdAt"] as string),
|
|
587
|
+
// };
|
|
588
|
+
// }
|
|
589
|
+
// }
|
|
590
|
+
|
|
591
|
+
const json = {
|
|
592
|
+
id: "123",
|
|
593
|
+
name: "Alice",
|
|
594
|
+
createdAt: "2024-01-15T00:00:00.000Z"
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
const profile = UserProfile.fromJSON(json);
|
|
598
|
+
console.log(profile.createdAt instanceof Date); // true
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
For union types, basic validation is applied:
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
/** @derive(Deserialize) */
|
|
605
|
+
type ApiStatus = "loading" | "success" | "error";
|
|
606
|
+
|
|
607
|
+
const status = ApiStatus.fromJSON("success");
|
|
608
|
+
console.log(status); // "success"
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
## Combining with Serialize
|
|
612
|
+
|
|
613
|
+
Use both Serialize and Deserialize for complete JSON round-trip support:
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
/** @derive(Serialize, Deserialize) */
|
|
617
|
+
/** @serde({ rename_all: "camelCase" }) */
|
|
618
|
+
class UserProfile {
|
|
619
|
+
user_name: string;
|
|
620
|
+
created_at: Date;
|
|
621
|
+
is_active: boolean;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Create and serialize
|
|
625
|
+
const profile = new UserProfile();
|
|
626
|
+
profile.user_name = "Alice";
|
|
627
|
+
profile.created_at = new Date();
|
|
628
|
+
profile.is_active = true;
|
|
629
|
+
|
|
630
|
+
const json = JSON.stringify(profile);
|
|
631
|
+
// {"userName":"Alice","createdAt":"2024-...","isActive":true}
|
|
632
|
+
|
|
633
|
+
// Deserialize back
|
|
634
|
+
const restored = UserProfile.fromJSON(JSON.parse(json));
|
|
635
|
+
console.log(restored.user_name); // "Alice"
|
|
636
|
+
console.log(restored.created_at instanceof Date); // true
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
## Error Handling
|
|
640
|
+
|
|
641
|
+
Handle deserialization errors gracefully:
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
/** @derive(Deserialize) */
|
|
645
|
+
class User {
|
|
646
|
+
name: string;
|
|
647
|
+
email: string;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function parseUser(json: unknown): User | null {
|
|
651
|
+
try {
|
|
652
|
+
return User.fromJSON(json);
|
|
653
|
+
} catch (error) {
|
|
654
|
+
console.error("Failed to parse user:", error.message);
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const user = parseUser({ name: "Alice" });
|
|
660
|
+
// Logs: Failed to parse user: User.fromJSON: missing required field "email"
|
|
661
|
+
// Returns: null
|
|
662
|
+
```
|