ts-serializable 4.2.2 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,28 +1,232 @@
1
- Serializable
2
- =====
1
+ # ts-serializable
2
+
3
+ > Powerful and flexible TypeScript/JavaScript library for serialization and deserialization with decorators
4
+
5
+ [![npm version](https://img.shields.io/npm/v/ts-serializable.svg)](https://www.npmjs.com/package/ts-serializable)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ }
3
70
 
4
- Small library for deserialization and serialization for JavaScript and TypeScript
71
+ // Deserialize from JSON
72
+ const json = { firstName: "John", lastName: "Doe", age: 30 };
73
+ const user = User.fromJSON(json);
5
74
 
6
- Description
7
- ------
75
+ console.log(user.getFullName()); // "John Doe"
76
+ console.log(user instanceof User); // true
8
77
 
9
- - For working, this library needs the Metadata Reflection API. If your platform (browser/Node.js) doesn't support it, you must use a polyfill. Example: [reflect-metadata](https://www.npmjs.com/package/reflect-metadata)
78
+ // Serialize back to JSON
79
+ const jsonOutput = user.toJSON();
80
+ console.log(JSON.stringify(jsonOutput)); // {"firstName":"John","lastName":"Doe","age":30}
81
+ ```
10
82
 
11
- - By default, the library doesn't crash on wrong types in JSON and returns the default value on the wrong property. If you need more secure behavior, you must override the method `onWrongType` on the `Serializable` object and throw an exception in this method, according to your logic.
83
+ ### Why Use ts-serializable?
12
84
 
13
- Installation
14
- ------
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
+ ```
15
91
 
16
- You can use the following command to install this package:
92
+ **With ts-serializable:**
17
93
 
18
- ``` bash
19
- npm install ts-serializable
94
+ ```typescript
95
+ const user: User = User.fromJSON(jsonString);
96
+ user.getFullName(); // ✅ Works perfectly and returns a string
20
97
  ```
21
98
 
22
- Usage
23
- ------
99
+ ## 🎓 Core Concepts
100
+
101
+ ### Type Safety
24
102
 
25
- This example is written in TypeScript, but if you remove typing, it will also work in JavaScript.
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.
104
+
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
+ ```
136
+
137
+ ## 🎨 Decorators
138
+
139
+ ### @jsonProperty
140
+
141
+ Specifies the accepted types for a property during deserialization.
142
+
143
+ ```typescript
144
+ @jsonProperty(...types: AcceptedTypes[])
145
+ ```
146
+
147
+ **Examples:**
148
+
149
+ ```typescript
150
+ // Single type
151
+ @jsonProperty(String)
152
+ public name: string = '';
153
+
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;
169
+ ```
170
+
171
+ ### @jsonIgnore
172
+
173
+ Excludes a property from serialization.
174
+
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,182 @@ user.getFullName(); // works fine and returns a string
88
292
  user.getAge(); // works fine and returns a number
89
293
  ```
90
294
 
91
- Naming strategies
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:
93
300
 
94
- Supported conversion between different naming cases, such as SnakeCase, KebabCase, PascalCase and CamelCase. Also, you can set a custom name for a property of a JSON object.
301
+ The `fromJSON` function deserializes JSON data into a class instance. It can accept either an existing object instance or a class constructor.
302
+
303
+ **Usage with instance:**
304
+
305
+ ```typescript
306
+ import { fromJSON, jsonProperty } from "ts-serializable";
307
+
308
+ class Product {
309
+ @jsonProperty(String)
310
+ public name: string = '';
311
+
312
+ @jsonProperty(Number)
313
+ public price: number = 0;
314
+
315
+ @jsonProperty(Date)
316
+ public releaseDate: Date = new Date();
317
+ }
318
+
319
+ const json = {
320
+ name: "Laptop",
321
+ price: 999.99,
322
+ releaseDate: "2024-01-15T10:00:00.000Z"
323
+ };
324
+
325
+ // Pass an existing instance
326
+ const product = new Product();
327
+ fromJSON(product, json);
328
+
329
+ console.log(product.name); // "Laptop"
330
+ console.log(product.price); // 999.99
331
+ console.log(product.releaseDate instanceof Date); // true
332
+ ```
333
+
334
+ **Usage with class constructor:**
335
+
336
+ ```typescript
337
+ // Pass a class constructor - the function will create an instance automatically
338
+ const product = fromJSON(Product, json);
339
+
340
+ console.log(product instanceof Product); // true
341
+ console.log(product.name); // "Laptop"
342
+ console.log(product.price); // 999.99
343
+ ```
344
+
345
+ Benefits:
346
+
347
+ - Works with plain classes (no need to extend `Serializable`)
348
+ - Accepts both instance and constructor for flexibility
349
+ - Respects all decorators (`@jsonProperty`, `@jsonName`, `@jsonIgnore`)
350
+ - Supports naming strategies
351
+ - Handles nested objects and arrays
352
+ - Type-safe deserialization
353
+ - Perfect for generic programming patterns
354
+
355
+ toJSON Function:
356
+
357
+ The `toJSON` function serializes an object to a plain JavaScript object, respecting decorators and naming strategies.
358
+
359
+ ```typescript
360
+ import { toJSON, jsonProperty, jsonIgnore, jsonName } from "ts-serializable";
361
+
362
+ class User {
363
+ @jsonProperty(String)
364
+ public firstName: string = 'John';
365
+
366
+ @jsonProperty(String)
367
+ @jsonName("family_name")
368
+ public lastName: string = 'Doe';
369
+
370
+ @jsonIgnore()
371
+ public password: string = 'secret123';
372
+
373
+ @jsonProperty(Number)
374
+ public age: number = 30;
375
+ }
376
+
377
+ const user = new User();
378
+ const json = toJSON(user);
379
+
380
+ console.log(json);
381
+ // Output: {
382
+ // firstName: "John",
383
+ // family_name: "Doe",
384
+ // age: 30
385
+ // }
386
+ // Note: password is excluded due to @jsonIgnore
387
+ ```
388
+
389
+ Benefits:
390
+
391
+ - Works with both `Serializable` instances and plain objects
392
+ - Respects `@jsonIgnore` decorator
393
+ - Applies `@jsonName` transformations
394
+ - Supports naming strategies
395
+ - Returns plain object ready for `JSON.stringify()`
396
+
397
+ Using Functions Together:
398
+
399
+ You can use both functions together for complete serialization/deserialization workflows:
400
+
401
+ ```typescript
402
+ import { fromJSON, toJSON, jsonProperty, jsonObject } from "ts-serializable";
403
+ import { SnakeCaseNamingStrategy } from "ts-serializable";
404
+
405
+ @jsonObject({ namingStrategy: new SnakeCaseNamingStrategy() })
406
+ class ApiRequest {
407
+ @jsonProperty(String)
408
+ public requestId: string = '';
409
+
410
+ @jsonProperty(String)
411
+ public userName: string = '';
412
+
413
+ @jsonProperty([String])
414
+ public userTags: string[] = [];
415
+ }
416
+
417
+ // Deserialize from API response using constructor
418
+ const apiData = {
419
+ request_id: "REQ-12345",
420
+ user_name: "john_doe",
421
+ user_tags: ["premium", "verified"]
422
+ };
423
+
424
+ // Using class constructor - creates new instance automatically
425
+ const request = fromJSON(ApiRequest, apiData);
426
+
427
+ console.log(request instanceof ApiRequest); // true
428
+ console.log(request.requestId); // "REQ-12345"
429
+ console.log(request.userName); // "john_doe"
430
+
431
+ // Serialize for sending to API
432
+ const jsonToSend = toJSON(request);
433
+ console.log(jsonToSend);
434
+ // Output: {
435
+ // request_id: "REQ-12345",
436
+ // user_name: "john_doe",
437
+ // user_tags: ["premium", "verified"]
438
+ // }
439
+ ```
440
+
441
+ **Alternative approach using instance:**
442
+
443
+ ```typescript
444
+ // Using instance
445
+ const request = new ApiRequest();
446
+ fromJSON(request, apiData);
447
+
448
+ console.log(request.requestId); // "REQ-12345"
449
+ console.log(request.userName); // "john_doe"
450
+
451
+ // Serialize for sending to API
452
+ const jsonToSend = toJSON(request);
453
+ console.log(jsonToSend);
454
+ // Output: {
455
+ // request_id: "REQ-12345",
456
+ // user_name: "john_doe",
457
+ // user_tags: ["premium", "verified"]
458
+ // }
459
+ ```
460
+
461
+ ## 🐍 Naming Strategies
462
+
463
+ The library supports automatic conversion between different naming conventions, making it easy to work with APIs that use different naming styles. Supported strategies include:
464
+
465
+ - **SnakeCaseNamingStrategy** - `user_name`
466
+ - **CamelCaseNamingStrategy** - `userName`
467
+ - **PascalCaseNamingStrategy** - `UserName`
468
+ - **KebabCaseNamingStrategy** - `user-name`
469
+
470
+ You can also use the `@jsonName` decorator for custom property names.
95
471
 
96
472
  ```typescript
97
473
  const json = {
@@ -127,10 +503,9 @@ user.dateOfBirth?.toISOString() === json.date_of_birth; // true
127
503
  user.veryStrangePropertyName === json["very::strange::json:name"]; // true
128
504
  ```
129
505
 
130
- Settings
131
- ------
506
+ ## ⚙️ Configuration Settings
132
507
 
133
- How to specify settings:
508
+ You can customize serialization behavior at three levels:
134
509
 
135
510
  ```typescript
136
511
  // Global settings
@@ -154,10 +529,9 @@ Supported settings:
154
529
  - **defaultValueHandling**, enum, default Ignore - ...coming soon.
155
530
  - **logLevel**, enum, default Warning - ...coming soon.
156
531
 
157
- View-Models from Backend Models
158
- ------
532
+ ## 🎭 View Models and DTOs
159
533
 
160
- If you need to create a view-model from a DTO or entities model, you can use the same model. Just add a VM property to the DTO or entities model and mark this property with the @jsonIgnore() decorator, and this property will not be serialized to JSON.
534
+ 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
535
 
162
536
  ```typescript
163
537
  import { jsonProperty, jsonIgnore, Serializable } from "ts-serializable";
@@ -181,38 +555,350 @@ JSON.stringify(user);
181
555
  // Result: {"firstName":"","familyName":""}
182
556
  ```
183
557
 
184
- Class to FormData
185
- ------
558
+ ## 📤 FormData Conversion
186
559
 
187
- Sometimes classes contain properties with the File type. Sending such classes via JSON is a heavy task. Converting a file property to JSON can freeze the interface for a few seconds if the file is large. A much better solution is to send an Ajax form. Example:
560
+ 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
561
 
189
- ```typescript
190
- import { Serializable } from "ts-serializable";
562
+ ### Basic Usage
191
563
 
192
- export class User extends Serializable {
564
+ ```typescript
565
+ import { Serializable, jsonProperty } from "ts-serializable";
193
566
 
194
- public firstName: string = '';
567
+ class UserProfile extends Serializable {
568
+ @jsonProperty(String)
569
+ public name: string = '';
195
570
 
196
- public familyName: File | null = null;
571
+ @jsonProperty(Number)
572
+ public age: number = 0;
197
573
 
574
+ @jsonProperty(File, null)
575
+ public avatar: File | null = null;
198
576
  }
199
577
 
200
- // ... send file function ...
578
+ const profile = new UserProfile();
579
+ profile.name = "John Doe";
580
+ profile.age = 30;
581
+ profile.avatar = fileInput.files[0]; // File from <input type="file">
201
582
 
202
- await fetch("api/sendFile", {
583
+ // Convert to FormData
584
+ const formData = profile.toFormData();
585
+
586
+ // Send via fetch
587
+ await fetch("/api/profile", {
203
588
  method: "POST",
204
- body: user.toFormData() // <- serialization class to FormData
589
+ body: formData
205
590
  });
591
+ ```
592
+
593
+ **Resulting FormData entries:**
594
+
595
+ ```text
596
+ name: "John Doe"
597
+ age: "30"
598
+ avatar: [File object]
599
+ ```
600
+
601
+ ### Complex Object Graphs
602
+
603
+ The library handles nested objects and arrays intelligently, using dot notation for nested properties and indices for arrays:
604
+
605
+ ```typescript
606
+ import { Serializable, jsonProperty, jsonIgnore } from "ts-serializable";
607
+
608
+ class Address extends Serializable {
609
+ @jsonProperty(String)
610
+ public street: string = '';
611
+
612
+ @jsonProperty(String)
613
+ public city: string = '';
614
+
615
+ @jsonProperty(String)
616
+ public country: string = '';
617
+ }
618
+
619
+ class Document extends Serializable {
620
+ @jsonProperty(String)
621
+ public title: string = '';
622
+
623
+ @jsonProperty(File, null)
624
+ public file: File | null = null;
625
+ }
626
+
627
+ class Employee extends Serializable {
628
+ @jsonProperty(String)
629
+ public firstName: string = '';
630
+
631
+ @jsonProperty(String)
632
+ public lastName: string = '';
633
+
634
+ @jsonProperty(Number)
635
+ public salary: number = 0;
636
+
637
+ @jsonProperty(Address)
638
+ public homeAddress: Address = new Address();
639
+
640
+ @jsonProperty([Document])
641
+ public documents: Document[] = [];
642
+
643
+ @jsonProperty(File, null)
644
+ public photo: File | null = null;
645
+
646
+ @jsonIgnore()
647
+ public password: string = ''; // Will be excluded
648
+ }
649
+
650
+ // Create instance with nested data
651
+ const employee = new Employee();
652
+ employee.firstName = "John";
653
+ employee.lastName = "Doe";
654
+ employee.salary = 75000;
655
+
656
+ employee.homeAddress.street = "123 Main St";
657
+ employee.homeAddress.city = "New York";
658
+ employee.homeAddress.country = "USA";
659
+
660
+ const doc1 = new Document();
661
+ doc1.title = "Resume";
662
+ doc1.file = resumeFile; // File object
663
+
664
+ const doc2 = new Document();
665
+ doc2.title = "ID Card";
666
+ doc2.file = idCardFile; // File object
667
+
668
+ employee.documents = [doc1, doc2];
669
+ employee.photo = photoFile; // File object
670
+ employee.password = "secret123"; // Will be ignored
671
+
672
+ // Convert to FormData
673
+ const formData = employee.toFormData();
674
+
675
+ // Inspect the FormData
676
+ for (const [key, value] of formData.entries()) {
677
+ console.log(key, value);
678
+ }
679
+ ```
680
+
681
+ **Resulting FormData structure:**
682
+
683
+ ```text
684
+ firstName: "John"
685
+ lastName: "Doe"
686
+ salary: "75000"
687
+ homeAddress.street: "123 Main St"
688
+ homeAddress.city: "New York"
689
+ homeAddress.country: "USA"
690
+ documents[0].title: "Resume"
691
+ documents[0].file: [File object - resume.pdf]
692
+ documents[1].title: "ID Card"
693
+ documents[1].file: [File object - id-card.jpg]
694
+ photo: [File object - photo.jpg]
695
+ ```
696
+
697
+ **Note:** The `password` property is excluded because of `@jsonIgnore()`.
698
+
699
+ ### With Custom Prefix
700
+
701
+ You can add a prefix to all form field names:
702
+
703
+ ```typescript
704
+ const formData = employee.toFormData("employee");
705
+
706
+ // Results in:
707
+ // employee.firstName: "John"
708
+ // employee.lastName: "Doe"
709
+ // employee.homeAddress.street: "123 Main St"
710
+ // etc.
711
+ ```
712
+
713
+ ### Appending to Existing FormData
714
+
715
+ You can append to an existing FormData instance:
206
716
 
717
+ ```typescript
718
+ const existingFormData = new FormData();
719
+ existingFormData.append("companyId", "12345");
720
+ existingFormData.append("department", "Engineering");
721
+
722
+ // Append employee data
723
+ employee.toFormData("employee", existingFormData);
724
+
725
+ // existingFormData now contains:
726
+ // companyId: "12345"
727
+ // department: "Engineering"
728
+ // employee.firstName: "John"
729
+ // employee.lastName: "Doe"
730
+ // ... etc.
207
731
  ```
208
732
 
209
- Naming strategies, custom names, ignoring and other decorators are supported during conversion.
733
+ ### Special Type Handling
734
+
735
+ The FormData conversion handles different types intelligently:
736
+
737
+ | Type | Conversion |
738
+ |------|------------|
739
+ | `string`, `number`, `boolean` | Converted to string |
740
+ | `File` | Added as-is (native File object) |
741
+ | `Date` | Converted to ISO string |
742
+ | `null` | Skipped (not added to FormData) |
743
+ | `undefined` | Skipped (not added to FormData) |
744
+ | `Array` | Items added with `[index]` notation |
745
+ | `Object` | Properties added with dot notation |
746
+
747
+ **Note:** All decorators (`@jsonIgnore`, `@jsonName`, naming strategies) are respected during FormData conversion.
748
+
749
+ ## 💡 Additional Features
210
750
 
211
- Bonus
212
- ------
751
+ ### Deep Copy
213
752
 
214
- Deep copy
753
+ Create a deep copy of an object by deserializing it:
215
754
 
216
755
  ```typescript
217
- const newUser: User = new User().fromJSON(oldUser);
756
+ const originalUser = new User();
757
+ originalUser.firstName = "John";
758
+ originalUser.age = 30;
759
+
760
+ const copiedUser: User = new User().fromJSON(originalUser);
761
+ // copiedUser is a completely separate instance with the same values
218
762
  ```
763
+
764
+ ### Nested Objects
765
+
766
+ Handle complex object hierarchies with ease:
767
+
768
+ ```typescript
769
+ class Address extends Serializable {
770
+ @jsonProperty(String)
771
+ public street: string = '';
772
+
773
+ @jsonProperty(String)
774
+ public city: string = '';
775
+ }
776
+
777
+ class User extends Serializable {
778
+ @jsonProperty(String)
779
+ public name: string = '';
780
+
781
+ @jsonProperty(Address)
782
+ public address: Address = new Address();
783
+ }
784
+
785
+ const json = {
786
+ name: "John",
787
+ address: {
788
+ street: "123 Main St",
789
+ city: "New York"
790
+ }
791
+ };
792
+
793
+ const user = User.fromJSON(json);
794
+ console.log(user.address instanceof Address); // true
795
+ ```
796
+
797
+ ### Arrays of Objects
798
+
799
+ ```typescript
800
+ class Team extends Serializable {
801
+ @jsonProperty(String)
802
+ public name: string = '';
803
+
804
+ @jsonProperty([User])
805
+ public members: User[] = [];
806
+ }
807
+
808
+ const json = {
809
+ name: "Dev Team",
810
+ members: [
811
+ { firstName: "John", lastName: "Doe", age: 30 },
812
+ { firstName: "Jane", lastName: "Smith", age: 28 }
813
+ ]
814
+ };
815
+
816
+ const team = Team.fromJSON(json);
817
+ console.log(team.members[0] instanceof User); // true
818
+ ```
819
+
820
+ ## 📚 API Reference
821
+
822
+ ### Serializable Class Methods
823
+
824
+ #### Static Methods
825
+
826
+ - **`fromJSON<T>(json: object, settings?: Partial<SerializationSettings>): T`**
827
+
828
+ Creates a new instance and deserializes JSON data into it.
829
+
830
+ - **`fromString<T>(str: string, settings?: Partial<SerializationSettings>): T`**
831
+
832
+ Parses a JSON string and deserializes it into a new instance.
833
+
834
+ #### Instance Methods
835
+
836
+ - **`fromJSON(json: object, settings?: Partial<SerializationSettings>): this`**
837
+
838
+ Populates the current instance with data from JSON.
839
+
840
+ - **`fromString(str: string, settings?: Partial<SerializationSettings>): this`**
841
+
842
+ Parses a JSON string and populates the current instance.
843
+
844
+ - **`toJSON(): Record<string, unknown>`**
845
+
846
+ Serializes the instance to a plain JavaScript object.
847
+
848
+ - **`toString(): string`**
849
+
850
+ Serializes the instance to a JSON string.
851
+
852
+ - **`toFormData(formPrefix?: string, formData?: FormData): FormData`**
853
+
854
+ Converts the instance to FormData for multipart requests.
855
+
856
+ - **`onWrongType(prop: string, message: string, value: unknown): void`**
857
+
858
+ Error handler for type mismatches. Override to customize error behavior.
859
+
860
+ ### Standalone Functions
861
+
862
+ - **`fromJSON<T>(obj: T | (new () => T), json: object, settings?: Partial<SerializationSettings>): T`**
863
+
864
+ Deserializes JSON into an object instance. Accepts either:
865
+ - An existing object instance to populate
866
+ - A class constructor to create a new instance
867
+
868
+ **Examples:**
869
+
870
+ ```typescript
871
+ // With instance
872
+ const product = new Product();
873
+ fromJSON(product, jsonData);
874
+
875
+ // With constructor
876
+ const product = fromJSON(Product, jsonData);
877
+ ```
878
+
879
+ - **`toJSON(obj: Serializable | object): Record<string, unknown>`**
880
+
881
+ Serializes an object to a plain JavaScript object.
882
+
883
+ - **`classToFormData(obj: object, formPrefix?: string, formData?: FormData): FormData`**
884
+
885
+ Converts an object to FormData format.
886
+
887
+ ### Available Naming Strategies
888
+
889
+ - `SnakeCaseNamingStrategy` - Converts to snake_case
890
+ - `CamelCaseNamingStrategy` - Converts to camelCase
891
+ - `PascalCaseNamingStrategy` - Converts to PascalCase
892
+ - `KebabCaseNamingStrategy` - Converts to kebab-case
893
+
894
+ ## 🤝 Contributing
895
+
896
+ Contributions are welcome! Please feel free to submit a Pull Request.
897
+
898
+ ## 📄 License
899
+
900
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
901
+
902
+ ## 🙏 Acknowledgments
903
+
904
+ Special thanks to all contributors and users of this library.