ts-serializable 4.2.2 → 4.3.2
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/README.md +676 -40
- package/dist/classes/Serializable.d.ts +147 -66
- package/dist/classes/Serializable.js +153 -191
- package/dist/functions/ClassToFormData.d.ts +34 -0
- package/dist/{utils → functions}/ClassToFormData.js +31 -6
- package/dist/functions/DeserializeProperty.d.ts +38 -0
- package/dist/functions/DeserializeProperty.js +142 -0
- package/dist/functions/FromJSON.d.ts +56 -0
- package/dist/functions/FromJSON.js +97 -0
- package/dist/functions/GetPropertyName.d.ts +38 -0
- package/dist/functions/GetPropertyName.js +56 -0
- package/dist/functions/OnWrongType.d.ts +23 -0
- package/dist/functions/OnWrongType.js +27 -0
- package/dist/functions/ToJSON.d.ts +36 -0
- package/dist/functions/ToJSON.js +57 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +7 -3
- package/package.json +7 -7
- package/dist/utils/ClassToFormData.d.ts +0 -9
- package/dist/utils/GetProperyName.d.ts +0 -10
- package/dist/utils/GetProperyName.js +0 -28
package/README.md
CHANGED
|
@@ -1,28 +1,232 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# ts-serializable
|
|
2
|
+
|
|
3
|
+
> Powerful and flexible TypeScript/JavaScript library for serialization and deserialization with decorators
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/ts-serializable)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## ✨ Features
|
|
9
|
+
|
|
10
|
+
- 🎯 **Type-safe** - Convert JSON to strongly-typed class instances
|
|
11
|
+
- 🎨 **Decorator-based** - Clean and intuitive API using TypeScript decorators
|
|
12
|
+
- 🔄 **Bidirectional** - Serialize to JSON and deserialize from JSON
|
|
13
|
+
- 🐍 **Naming Strategies** - Support for snake_case, camelCase, PascalCase, kebab-case
|
|
14
|
+
- 📦 **Nested Objects** - Handle complex object hierarchies and arrays
|
|
15
|
+
- 🔒 **Flexible** - Works with or without class inheritance
|
|
16
|
+
- 📝 **FormData Support** - Built-in conversion to FormData for file uploads
|
|
17
|
+
- ⚡ **Lightweight** - Minimal dependencies and small bundle size
|
|
18
|
+
|
|
19
|
+
## 📋 Table of Contents
|
|
20
|
+
|
|
21
|
+
- [Installation](#-installation)
|
|
22
|
+
- [Quick Start](#-quick-start)
|
|
23
|
+
- [Core Concepts](#-core-concepts)
|
|
24
|
+
- [Decorators](#-decorators)
|
|
25
|
+
- [Advanced Usage](#-advanced-usage)
|
|
26
|
+
- [Standalone Functions](#-standalone-functions)
|
|
27
|
+
- [Naming Strategies](#-naming-strategies)
|
|
28
|
+
- [Configuration Settings](#️-configuration-settings)
|
|
29
|
+
- [View Models and DTOs](#-view-models-and-dtos)
|
|
30
|
+
- [FormData Conversion](#-formdata-conversion)
|
|
31
|
+
- [Additional Features](#-additional-features)
|
|
32
|
+
- [API Reference](#-api-reference)
|
|
33
|
+
- [Contributing](#-contributing)
|
|
34
|
+
- [License](#-license)
|
|
35
|
+
|
|
36
|
+
## 🚀 Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install ts-serializable reflect-metadata
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Important:** This library requires the Metadata Reflection API. Import `reflect-metadata` at the entry point of your application:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// At the top of your main file (e.g., index.ts or main.ts)
|
|
46
|
+
import "reflect-metadata";
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 🎯 Quick Start
|
|
50
|
+
|
|
51
|
+
Here's a simple example to get you started:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { jsonProperty, Serializable } from "ts-serializable";
|
|
55
|
+
|
|
56
|
+
class User extends Serializable {
|
|
57
|
+
@jsonProperty(String)
|
|
58
|
+
public firstName: string = '';
|
|
59
|
+
|
|
60
|
+
@jsonProperty(String)
|
|
61
|
+
public lastName: string = '';
|
|
62
|
+
|
|
63
|
+
@jsonProperty(Number)
|
|
64
|
+
public age: number = 0;
|
|
65
|
+
|
|
66
|
+
public getFullName(): string {
|
|
67
|
+
return `${this.firstName} ${this.lastName}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Deserialize from JSON
|
|
72
|
+
const json = { firstName: "John", lastName: "Doe", age: 30 };
|
|
73
|
+
const user = User.fromJSON(json);
|
|
74
|
+
|
|
75
|
+
console.log(user.getFullName()); // "John Doe"
|
|
76
|
+
console.log(user instanceof User); // true
|
|
77
|
+
|
|
78
|
+
// Serialize back to JSON
|
|
79
|
+
const jsonOutput = user.toJSON();
|
|
80
|
+
console.log(JSON.stringify(jsonOutput)); // {"firstName":"John","lastName":"Doe","age":30}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Why Use ts-serializable?
|
|
84
|
+
|
|
85
|
+
**Without ts-serializable:**
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const user: object = JSON.parse(jsonString);
|
|
89
|
+
user.getFullName(); // ❌ Runtime Error: user.getFullName is not a function
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**With ts-serializable:**
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
const user: User = User.fromJSON(jsonString);
|
|
96
|
+
user.getFullName(); // ✅ Works perfectly and returns a string
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 🎓 Core Concepts
|
|
100
|
+
|
|
101
|
+
### Type Safety
|
|
3
102
|
|
|
4
|
-
|
|
103
|
+
The `@jsonProperty` decorator tells the library what types are acceptable for each property. If a JSON value doesn't match the expected type, the property will retain its default value.
|
|
5
104
|
|
|
6
|
-
|
|
7
|
-
|
|
105
|
+
```typescript
|
|
106
|
+
class Product extends Serializable {
|
|
107
|
+
@jsonProperty(String)
|
|
108
|
+
public name: string = '';
|
|
109
|
+
|
|
110
|
+
@jsonProperty(Number)
|
|
111
|
+
public price: number = 0;
|
|
112
|
+
|
|
113
|
+
@jsonProperty(Date)
|
|
114
|
+
public releaseDate: Date = new Date();
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Default Values
|
|
119
|
+
|
|
120
|
+
Always provide default values for properties decorated with `@jsonProperty`. This ensures type safety and provides fallback values when deserialization encounters issues.
|
|
121
|
+
|
|
122
|
+
### Error Handling
|
|
123
|
+
|
|
124
|
+
By default, the library logs errors to the console but doesn't throw exceptions. For stricter behavior, override the `onWrongType` method:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
class StrictUser extends Serializable {
|
|
128
|
+
@jsonProperty(String)
|
|
129
|
+
public name: string = '';
|
|
130
|
+
|
|
131
|
+
protected onWrongType(prop: string, message: string, value: unknown): void {
|
|
132
|
+
throw new Error(`Invalid property "${prop}": ${message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
8
136
|
|
|
9
|
-
|
|
137
|
+
## 🎨 Decorators
|
|
10
138
|
|
|
11
|
-
|
|
139
|
+
### @jsonProperty
|
|
12
140
|
|
|
13
|
-
|
|
14
|
-
------
|
|
141
|
+
Specifies the accepted types for a property during deserialization.
|
|
15
142
|
|
|
16
|
-
|
|
143
|
+
```typescript
|
|
144
|
+
@jsonProperty(...types: AcceptedTypes[])
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Examples:**
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Single type
|
|
151
|
+
@jsonProperty(String)
|
|
152
|
+
public name: string = '';
|
|
17
153
|
|
|
18
|
-
|
|
19
|
-
|
|
154
|
+
// Multiple types (union)
|
|
155
|
+
@jsonProperty(Number, null)
|
|
156
|
+
public age: number | null = null;
|
|
157
|
+
|
|
158
|
+
// Arrays
|
|
159
|
+
@jsonProperty([String])
|
|
160
|
+
public tags: string[] = [];
|
|
161
|
+
|
|
162
|
+
// Nested objects
|
|
163
|
+
@jsonProperty(Address)
|
|
164
|
+
public address: Address = new Address();
|
|
165
|
+
|
|
166
|
+
// Optional properties
|
|
167
|
+
@jsonProperty(String, void 0)
|
|
168
|
+
public middleName?: string = void 0;
|
|
20
169
|
```
|
|
21
170
|
|
|
22
|
-
|
|
23
|
-
|
|
171
|
+
### @jsonIgnore
|
|
172
|
+
|
|
173
|
+
Excludes a property from serialization.
|
|
24
174
|
|
|
25
|
-
|
|
175
|
+
```typescript
|
|
176
|
+
@jsonIgnore()
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Example:**
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
class User extends Serializable {
|
|
183
|
+
@jsonProperty(String)
|
|
184
|
+
public username: string = '';
|
|
185
|
+
|
|
186
|
+
@jsonIgnore()
|
|
187
|
+
public password: string = ''; // Won't be included in toJSON()
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### @jsonName
|
|
192
|
+
|
|
193
|
+
Specifies a custom JSON property name.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
@jsonName(name: string)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Example:**
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
class User extends Serializable {
|
|
203
|
+
@jsonName("user_id")
|
|
204
|
+
@jsonProperty(Number)
|
|
205
|
+
public userId: number = 0; // Maps to "user_id" in JSON
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### @jsonObject
|
|
210
|
+
|
|
211
|
+
Configures serialization settings at the class level.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
@jsonObject(settings?: Partial<SerializationSettings>)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Example:**
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
@jsonObject({ namingStrategy: new SnakeCaseNamingStrategy() })
|
|
221
|
+
class User extends Serializable {
|
|
222
|
+
@jsonProperty(String)
|
|
223
|
+
public firstName: string = ''; // Automatically maps to "first_name"
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## 🔧 Advanced Usage
|
|
228
|
+
|
|
229
|
+
This example is written in TypeScript, but it also works in JavaScript (without type annotations).
|
|
26
230
|
|
|
27
231
|
```typescript
|
|
28
232
|
import { jsonProperty, Serializable } from "ts-serializable";
|
|
@@ -88,10 +292,145 @@ user.getFullName(); // works fine and returns a string
|
|
|
88
292
|
user.getAge(); // works fine and returns a number
|
|
89
293
|
```
|
|
90
294
|
|
|
91
|
-
|
|
92
|
-
|
|
295
|
+
## 🔧 Standalone Functions
|
|
296
|
+
|
|
297
|
+
The library provides standalone utility functions `fromJSON` and `toJSON` that can be used with any objects, not just classes that extend `Serializable`. This is useful when you want to use the serialization features without inheritance.
|
|
298
|
+
|
|
299
|
+
fromJSON Function:
|
|
300
|
+
|
|
301
|
+
The `fromJSON` function populates an existing object instance with data from JSON, using decorator metadata for type conversion.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { fromJSON, jsonProperty } from "ts-serializable";
|
|
305
|
+
|
|
306
|
+
class Product {
|
|
307
|
+
@jsonProperty(String)
|
|
308
|
+
public name: string = '';
|
|
309
|
+
|
|
310
|
+
@jsonProperty(Number)
|
|
311
|
+
public price: number = 0;
|
|
312
|
+
|
|
313
|
+
@jsonProperty(Date)
|
|
314
|
+
public releaseDate: Date = new Date();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const json = {
|
|
318
|
+
name: "Laptop",
|
|
319
|
+
price: 999.99,
|
|
320
|
+
releaseDate: "2024-01-15T10:00:00.000Z"
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const product = new Product();
|
|
324
|
+
fromJSON(product, json);
|
|
325
|
+
|
|
326
|
+
console.log(product.name); // "Laptop"
|
|
327
|
+
console.log(product.price); // 999.99
|
|
328
|
+
console.log(product.releaseDate instanceof Date); // true
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Benefits:
|
|
332
|
+
|
|
333
|
+
- Works with plain classes (no need to extend `Serializable`)
|
|
334
|
+
- Respects all decorators (`@jsonProperty`, `@jsonName`, `@jsonIgnore`)
|
|
335
|
+
- Supports naming strategies
|
|
336
|
+
- Handles nested objects and arrays
|
|
337
|
+
- Type-safe deserialization
|
|
338
|
+
|
|
339
|
+
toJSON Function:
|
|
340
|
+
|
|
341
|
+
The `toJSON` function serializes an object to a plain JavaScript object, respecting decorators and naming strategies.
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { toJSON, jsonProperty, jsonIgnore, jsonName } from "ts-serializable";
|
|
345
|
+
|
|
346
|
+
class User {
|
|
347
|
+
@jsonProperty(String)
|
|
348
|
+
public firstName: string = 'John';
|
|
349
|
+
|
|
350
|
+
@jsonProperty(String)
|
|
351
|
+
@jsonName("family_name")
|
|
352
|
+
public lastName: string = 'Doe';
|
|
353
|
+
|
|
354
|
+
@jsonIgnore()
|
|
355
|
+
public password: string = 'secret123';
|
|
356
|
+
|
|
357
|
+
@jsonProperty(Number)
|
|
358
|
+
public age: number = 30;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const user = new User();
|
|
362
|
+
const json = toJSON(user);
|
|
363
|
+
|
|
364
|
+
console.log(json);
|
|
365
|
+
// Output: {
|
|
366
|
+
// firstName: "John",
|
|
367
|
+
// family_name: "Doe",
|
|
368
|
+
// age: 30
|
|
369
|
+
// }
|
|
370
|
+
// Note: password is excluded due to @jsonIgnore
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Benefits:
|
|
374
|
+
|
|
375
|
+
- Works with both `Serializable` instances and plain objects
|
|
376
|
+
- Respects `@jsonIgnore` decorator
|
|
377
|
+
- Applies `@jsonName` transformations
|
|
378
|
+
- Supports naming strategies
|
|
379
|
+
- Returns plain object ready for `JSON.stringify()`
|
|
380
|
+
|
|
381
|
+
Using Functions Together:
|
|
382
|
+
|
|
383
|
+
You can use both functions together for complete serialization/deserialization workflows:
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
import { fromJSON, toJSON, jsonProperty, jsonObject } from "ts-serializable";
|
|
387
|
+
import { SnakeCaseNamingStrategy } from "ts-serializable";
|
|
388
|
+
|
|
389
|
+
@jsonObject({ namingStrategy: new SnakeCaseNamingStrategy() })
|
|
390
|
+
class ApiRequest {
|
|
391
|
+
@jsonProperty(String)
|
|
392
|
+
public requestId: string = '';
|
|
393
|
+
|
|
394
|
+
@jsonProperty(String)
|
|
395
|
+
public userName: string = '';
|
|
396
|
+
|
|
397
|
+
@jsonProperty([String])
|
|
398
|
+
public userTags: string[] = [];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Deserialize from API response
|
|
402
|
+
const apiData = {
|
|
403
|
+
request_id: "REQ-12345",
|
|
404
|
+
user_name: "john_doe",
|
|
405
|
+
user_tags: ["premium", "verified"]
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const request = new ApiRequest();
|
|
409
|
+
fromJSON(request, apiData);
|
|
410
|
+
|
|
411
|
+
console.log(request.requestId); // "REQ-12345"
|
|
412
|
+
console.log(request.userName); // "john_doe"
|
|
413
|
+
|
|
414
|
+
// Serialize for sending to API
|
|
415
|
+
const jsonToSend = toJSON(request);
|
|
416
|
+
console.log(jsonToSend);
|
|
417
|
+
// Output: {
|
|
418
|
+
// request_id: "REQ-12345",
|
|
419
|
+
// user_name: "john_doe",
|
|
420
|
+
// user_tags: ["premium", "verified"]
|
|
421
|
+
// }
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## 🐍 Naming Strategies
|
|
425
|
+
|
|
426
|
+
The library supports automatic conversion between different naming conventions, making it easy to work with APIs that use different naming styles. Supported strategies include:
|
|
93
427
|
|
|
94
|
-
|
|
428
|
+
- **SnakeCaseNamingStrategy** - `user_name`
|
|
429
|
+
- **CamelCaseNamingStrategy** - `userName`
|
|
430
|
+
- **PascalCaseNamingStrategy** - `UserName`
|
|
431
|
+
- **KebabCaseNamingStrategy** - `user-name`
|
|
432
|
+
|
|
433
|
+
You can also use the `@jsonName` decorator for custom property names.
|
|
95
434
|
|
|
96
435
|
```typescript
|
|
97
436
|
const json = {
|
|
@@ -127,10 +466,9 @@ user.dateOfBirth?.toISOString() === json.date_of_birth; // true
|
|
|
127
466
|
user.veryStrangePropertyName === json["very::strange::json:name"]; // true
|
|
128
467
|
```
|
|
129
468
|
|
|
130
|
-
Settings
|
|
131
|
-
------
|
|
469
|
+
## ⚙️ Configuration Settings
|
|
132
470
|
|
|
133
|
-
|
|
471
|
+
You can customize serialization behavior at three levels:
|
|
134
472
|
|
|
135
473
|
```typescript
|
|
136
474
|
// Global settings
|
|
@@ -154,10 +492,9 @@ Supported settings:
|
|
|
154
492
|
- **defaultValueHandling**, enum, default Ignore - ...coming soon.
|
|
155
493
|
- **logLevel**, enum, default Warning - ...coming soon.
|
|
156
494
|
|
|
157
|
-
View
|
|
158
|
-
------
|
|
495
|
+
## 🎭 View Models and DTOs
|
|
159
496
|
|
|
160
|
-
If you need to create
|
|
497
|
+
If you need to create view-models from DTO or entity models, you can add view-specific properties and mark them with `@jsonIgnore()` to exclude them from serialization.
|
|
161
498
|
|
|
162
499
|
```typescript
|
|
163
500
|
import { jsonProperty, jsonIgnore, Serializable } from "ts-serializable";
|
|
@@ -181,38 +518,337 @@ JSON.stringify(user);
|
|
|
181
518
|
// Result: {"firstName":"","familyName":""}
|
|
182
519
|
```
|
|
183
520
|
|
|
184
|
-
|
|
185
|
-
------
|
|
521
|
+
## 📤 FormData Conversion
|
|
186
522
|
|
|
187
|
-
|
|
523
|
+
When working with file uploads, converting files to JSON (base64) can freeze the UI for large files. The library provides built-in FormData conversion as a more efficient alternative.
|
|
188
524
|
|
|
189
|
-
|
|
190
|
-
import { Serializable } from "ts-serializable";
|
|
525
|
+
### Basic Usage
|
|
191
526
|
|
|
192
|
-
|
|
527
|
+
```typescript
|
|
528
|
+
import { Serializable, jsonProperty } from "ts-serializable";
|
|
193
529
|
|
|
194
|
-
|
|
530
|
+
class UserProfile extends Serializable {
|
|
531
|
+
@jsonProperty(String)
|
|
532
|
+
public name: string = '';
|
|
195
533
|
|
|
196
|
-
|
|
534
|
+
@jsonProperty(Number)
|
|
535
|
+
public age: number = 0;
|
|
197
536
|
|
|
537
|
+
@jsonProperty(File, null)
|
|
538
|
+
public avatar: File | null = null;
|
|
198
539
|
}
|
|
199
540
|
|
|
200
|
-
|
|
541
|
+
const profile = new UserProfile();
|
|
542
|
+
profile.name = "John Doe";
|
|
543
|
+
profile.age = 30;
|
|
544
|
+
profile.avatar = fileInput.files[0]; // File from <input type="file">
|
|
545
|
+
|
|
546
|
+
// Convert to FormData
|
|
547
|
+
const formData = profile.toFormData();
|
|
201
548
|
|
|
202
|
-
|
|
549
|
+
// Send via fetch
|
|
550
|
+
await fetch("/api/profile", {
|
|
203
551
|
method: "POST",
|
|
204
|
-
body:
|
|
552
|
+
body: formData
|
|
205
553
|
});
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
**Resulting FormData entries:**
|
|
557
|
+
|
|
558
|
+
```text
|
|
559
|
+
name: "John Doe"
|
|
560
|
+
age: "30"
|
|
561
|
+
avatar: [File object]
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Complex Object Graphs
|
|
565
|
+
|
|
566
|
+
The library handles nested objects and arrays intelligently, using dot notation for nested properties and indices for arrays:
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
import { Serializable, jsonProperty, jsonIgnore } from "ts-serializable";
|
|
570
|
+
|
|
571
|
+
class Address extends Serializable {
|
|
572
|
+
@jsonProperty(String)
|
|
573
|
+
public street: string = '';
|
|
574
|
+
|
|
575
|
+
@jsonProperty(String)
|
|
576
|
+
public city: string = '';
|
|
577
|
+
|
|
578
|
+
@jsonProperty(String)
|
|
579
|
+
public country: string = '';
|
|
580
|
+
}
|
|
206
581
|
|
|
582
|
+
class Document extends Serializable {
|
|
583
|
+
@jsonProperty(String)
|
|
584
|
+
public title: string = '';
|
|
585
|
+
|
|
586
|
+
@jsonProperty(File, null)
|
|
587
|
+
public file: File | null = null;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
class Employee extends Serializable {
|
|
591
|
+
@jsonProperty(String)
|
|
592
|
+
public firstName: string = '';
|
|
593
|
+
|
|
594
|
+
@jsonProperty(String)
|
|
595
|
+
public lastName: string = '';
|
|
596
|
+
|
|
597
|
+
@jsonProperty(Number)
|
|
598
|
+
public salary: number = 0;
|
|
599
|
+
|
|
600
|
+
@jsonProperty(Address)
|
|
601
|
+
public homeAddress: Address = new Address();
|
|
602
|
+
|
|
603
|
+
@jsonProperty([Document])
|
|
604
|
+
public documents: Document[] = [];
|
|
605
|
+
|
|
606
|
+
@jsonProperty(File, null)
|
|
607
|
+
public photo: File | null = null;
|
|
608
|
+
|
|
609
|
+
@jsonIgnore()
|
|
610
|
+
public password: string = ''; // Will be excluded
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Create instance with nested data
|
|
614
|
+
const employee = new Employee();
|
|
615
|
+
employee.firstName = "John";
|
|
616
|
+
employee.lastName = "Doe";
|
|
617
|
+
employee.salary = 75000;
|
|
618
|
+
|
|
619
|
+
employee.homeAddress.street = "123 Main St";
|
|
620
|
+
employee.homeAddress.city = "New York";
|
|
621
|
+
employee.homeAddress.country = "USA";
|
|
622
|
+
|
|
623
|
+
const doc1 = new Document();
|
|
624
|
+
doc1.title = "Resume";
|
|
625
|
+
doc1.file = resumeFile; // File object
|
|
626
|
+
|
|
627
|
+
const doc2 = new Document();
|
|
628
|
+
doc2.title = "ID Card";
|
|
629
|
+
doc2.file = idCardFile; // File object
|
|
630
|
+
|
|
631
|
+
employee.documents = [doc1, doc2];
|
|
632
|
+
employee.photo = photoFile; // File object
|
|
633
|
+
employee.password = "secret123"; // Will be ignored
|
|
634
|
+
|
|
635
|
+
// Convert to FormData
|
|
636
|
+
const formData = employee.toFormData();
|
|
637
|
+
|
|
638
|
+
// Inspect the FormData
|
|
639
|
+
for (const [key, value] of formData.entries()) {
|
|
640
|
+
console.log(key, value);
|
|
641
|
+
}
|
|
207
642
|
```
|
|
208
643
|
|
|
209
|
-
|
|
644
|
+
**Resulting FormData structure:**
|
|
645
|
+
|
|
646
|
+
```text
|
|
647
|
+
firstName: "John"
|
|
648
|
+
lastName: "Doe"
|
|
649
|
+
salary: "75000"
|
|
650
|
+
homeAddress.street: "123 Main St"
|
|
651
|
+
homeAddress.city: "New York"
|
|
652
|
+
homeAddress.country: "USA"
|
|
653
|
+
documents[0].title: "Resume"
|
|
654
|
+
documents[0].file: [File object - resume.pdf]
|
|
655
|
+
documents[1].title: "ID Card"
|
|
656
|
+
documents[1].file: [File object - id-card.jpg]
|
|
657
|
+
photo: [File object - photo.jpg]
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
**Note:** The `password` property is excluded because of `@jsonIgnore()`.
|
|
661
|
+
|
|
662
|
+
### With Custom Prefix
|
|
663
|
+
|
|
664
|
+
You can add a prefix to all form field names:
|
|
210
665
|
|
|
211
|
-
|
|
212
|
-
|
|
666
|
+
```typescript
|
|
667
|
+
const formData = employee.toFormData("employee");
|
|
668
|
+
|
|
669
|
+
// Results in:
|
|
670
|
+
// employee.firstName: "John"
|
|
671
|
+
// employee.lastName: "Doe"
|
|
672
|
+
// employee.homeAddress.street: "123 Main St"
|
|
673
|
+
// etc.
|
|
674
|
+
```
|
|
213
675
|
|
|
214
|
-
|
|
676
|
+
### Appending to Existing FormData
|
|
677
|
+
|
|
678
|
+
You can append to an existing FormData instance:
|
|
215
679
|
|
|
216
680
|
```typescript
|
|
217
|
-
const
|
|
681
|
+
const existingFormData = new FormData();
|
|
682
|
+
existingFormData.append("companyId", "12345");
|
|
683
|
+
existingFormData.append("department", "Engineering");
|
|
684
|
+
|
|
685
|
+
// Append employee data
|
|
686
|
+
employee.toFormData("employee", existingFormData);
|
|
687
|
+
|
|
688
|
+
// existingFormData now contains:
|
|
689
|
+
// companyId: "12345"
|
|
690
|
+
// department: "Engineering"
|
|
691
|
+
// employee.firstName: "John"
|
|
692
|
+
// employee.lastName: "Doe"
|
|
693
|
+
// ... etc.
|
|
218
694
|
```
|
|
695
|
+
|
|
696
|
+
### Special Type Handling
|
|
697
|
+
|
|
698
|
+
The FormData conversion handles different types intelligently:
|
|
699
|
+
|
|
700
|
+
| Type | Conversion |
|
|
701
|
+
|------|------------|
|
|
702
|
+
| `string`, `number`, `boolean` | Converted to string |
|
|
703
|
+
| `File` | Added as-is (native File object) |
|
|
704
|
+
| `Date` | Converted to ISO string |
|
|
705
|
+
| `null` | Skipped (not added to FormData) |
|
|
706
|
+
| `undefined` | Skipped (not added to FormData) |
|
|
707
|
+
| `Array` | Items added with `[index]` notation |
|
|
708
|
+
| `Object` | Properties added with dot notation |
|
|
709
|
+
|
|
710
|
+
**Note:** All decorators (`@jsonIgnore`, `@jsonName`, naming strategies) are respected during FormData conversion.
|
|
711
|
+
|
|
712
|
+
## 💡 Additional Features
|
|
713
|
+
|
|
714
|
+
### Deep Copy
|
|
715
|
+
|
|
716
|
+
Create a deep copy of an object by deserializing it:
|
|
717
|
+
|
|
718
|
+
```typescript
|
|
719
|
+
const originalUser = new User();
|
|
720
|
+
originalUser.firstName = "John";
|
|
721
|
+
originalUser.age = 30;
|
|
722
|
+
|
|
723
|
+
const copiedUser: User = new User().fromJSON(originalUser);
|
|
724
|
+
// copiedUser is a completely separate instance with the same values
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### Nested Objects
|
|
728
|
+
|
|
729
|
+
Handle complex object hierarchies with ease:
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
class Address extends Serializable {
|
|
733
|
+
@jsonProperty(String)
|
|
734
|
+
public street: string = '';
|
|
735
|
+
|
|
736
|
+
@jsonProperty(String)
|
|
737
|
+
public city: string = '';
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
class User extends Serializable {
|
|
741
|
+
@jsonProperty(String)
|
|
742
|
+
public name: string = '';
|
|
743
|
+
|
|
744
|
+
@jsonProperty(Address)
|
|
745
|
+
public address: Address = new Address();
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const json = {
|
|
749
|
+
name: "John",
|
|
750
|
+
address: {
|
|
751
|
+
street: "123 Main St",
|
|
752
|
+
city: "New York"
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
const user = User.fromJSON(json);
|
|
757
|
+
console.log(user.address instanceof Address); // true
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### Arrays of Objects
|
|
761
|
+
|
|
762
|
+
```typescript
|
|
763
|
+
class Team extends Serializable {
|
|
764
|
+
@jsonProperty(String)
|
|
765
|
+
public name: string = '';
|
|
766
|
+
|
|
767
|
+
@jsonProperty([User])
|
|
768
|
+
public members: User[] = [];
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const json = {
|
|
772
|
+
name: "Dev Team",
|
|
773
|
+
members: [
|
|
774
|
+
{ firstName: "John", lastName: "Doe", age: 30 },
|
|
775
|
+
{ firstName: "Jane", lastName: "Smith", age: 28 }
|
|
776
|
+
]
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
const team = Team.fromJSON(json);
|
|
780
|
+
console.log(team.members[0] instanceof User); // true
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
## 📚 API Reference
|
|
784
|
+
|
|
785
|
+
### Serializable Class Methods
|
|
786
|
+
|
|
787
|
+
#### Static Methods
|
|
788
|
+
|
|
789
|
+
- **`fromJSON<T>(json: object, settings?: Partial<SerializationSettings>): T`**
|
|
790
|
+
|
|
791
|
+
Creates a new instance and deserializes JSON data into it.
|
|
792
|
+
|
|
793
|
+
- **`fromString<T>(str: string, settings?: Partial<SerializationSettings>): T`**
|
|
794
|
+
|
|
795
|
+
Parses a JSON string and deserializes it into a new instance.
|
|
796
|
+
|
|
797
|
+
#### Instance Methods
|
|
798
|
+
|
|
799
|
+
- **`fromJSON(json: object, settings?: Partial<SerializationSettings>): this`**
|
|
800
|
+
|
|
801
|
+
Populates the current instance with data from JSON.
|
|
802
|
+
|
|
803
|
+
- **`fromString(str: string, settings?: Partial<SerializationSettings>): this`**
|
|
804
|
+
|
|
805
|
+
Parses a JSON string and populates the current instance.
|
|
806
|
+
|
|
807
|
+
- **`toJSON(): Record<string, unknown>`**
|
|
808
|
+
|
|
809
|
+
Serializes the instance to a plain JavaScript object.
|
|
810
|
+
|
|
811
|
+
- **`toString(): string`**
|
|
812
|
+
|
|
813
|
+
Serializes the instance to a JSON string.
|
|
814
|
+
|
|
815
|
+
- **`toFormData(formPrefix?: string, formData?: FormData): FormData`**
|
|
816
|
+
|
|
817
|
+
Converts the instance to FormData for multipart requests.
|
|
818
|
+
|
|
819
|
+
- **`onWrongType(prop: string, message: string, value: unknown): void`**
|
|
820
|
+
|
|
821
|
+
Error handler for type mismatches. Override to customize error behavior.
|
|
822
|
+
|
|
823
|
+
### Standalone Functions
|
|
824
|
+
|
|
825
|
+
- **`fromJSON<T>(obj: T, json: object, settings?: Partial<SerializationSettings>): T`**
|
|
826
|
+
|
|
827
|
+
Deserializes JSON into an existing object instance.
|
|
828
|
+
|
|
829
|
+
- **`toJSON(obj: Serializable | object): Record<string, unknown>`**
|
|
830
|
+
|
|
831
|
+
Serializes an object to a plain JavaScript object.
|
|
832
|
+
|
|
833
|
+
- **`classToFormData(obj: object, formPrefix?: string, formData?: FormData): FormData`**
|
|
834
|
+
|
|
835
|
+
Converts an object to FormData format.
|
|
836
|
+
|
|
837
|
+
### Available Naming Strategies
|
|
838
|
+
|
|
839
|
+
- `SnakeCaseNamingStrategy` - Converts to snake_case
|
|
840
|
+
- `CamelCaseNamingStrategy` - Converts to camelCase
|
|
841
|
+
- `PascalCaseNamingStrategy` - Converts to PascalCase
|
|
842
|
+
- `KebabCaseNamingStrategy` - Converts to kebab-case
|
|
843
|
+
|
|
844
|
+
## 🤝 Contributing
|
|
845
|
+
|
|
846
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
847
|
+
|
|
848
|
+
## 📄 License
|
|
849
|
+
|
|
850
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
851
|
+
|
|
852
|
+
## 🙏 Acknowledgments
|
|
853
|
+
|
|
854
|
+
Special thanks to all contributors and users of this library.
|