@qubit-ltd/common-decorator 3.8.10 → 3.9.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,4 +1,4 @@
1
- # js-common-decorator
1
+ # js-common-decorator
2
2
 
3
3
  [![npm package](https://img.shields.io/npm/v/@qubit-ltd/common-decorator.svg)](https://npmjs.com/package/@qubit-ltd/common-decorator)
4
4
  [![License](https://img.shields.io/badge/License-Apache-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
@@ -8,9 +8,29 @@
8
8
 
9
9
  [@qubit-ltd/common-decorator] is a JavaScript library of common decorators,
10
10
  provides decorators to add common methods to domain classes. The library
11
- supports the most recent (currently May 2023)
11
+ supports the most recent (currently November 2023)
12
12
  [stage 3 proposal of JavaScript decorators].
13
13
 
14
+ ## Features
15
+
16
+ - **Modern Decorator Support**: Compatible with the latest Stage 3 proposal for JavaScript decorators
17
+ - **Model Enhancement**: `@Model` decorator adds common methods to domain model classes
18
+ - **Enum Implementation**: `@Enum` decorator provides Java-like enumeration capabilities
19
+ - **Validation Support**: `@Validatable` decorator enables field validation
20
+ - **Normalization Support**: `@Normalizable` decorator enables field normalization
21
+ - **Type Safety**: `@Type` and `@ElementType` decorators for type checking
22
+ - **Serialization Utilities**: Built-in JSON serialization/deserialization support
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ # Using npm
28
+ npm install @qubit-ltd/common-decorator
29
+
30
+ # Using yarn
31
+ yarn add @qubit-ltd/common-decorator
32
+ ```
33
+
14
34
  ## <span id="content">Table of Contents</span>
15
35
 
16
36
  - [Usage](#usage)
@@ -1004,14 +1024,14 @@ expect(opt3.convertNaming).toBe(false);
1004
1024
 
1005
1025
  ## <span id="configuration">Configuration</span>
1006
1026
 
1007
- This library uses the most recent (currently May 2023)
1027
+ This library uses the most recent (currently November 2023)
1008
1028
  [stage 3 proposal of JavaScript decorators]. Therefore, you must configure
1009
1029
  [Babel] with [@babel/plugin-transform-class-properties] and the
1010
1030
  [@babel/plugin-proposal-decorators] plugins.
1011
1031
 
1012
- **NOTE:** To support the [stage 3 proposal of JavaScript decorator metadata],
1013
- the version of the [Babel] plugin [@babel/plugin-proposal-decorators] must be
1014
- at least `7.23.0`.
1032
+ **NOTE:** To support the [stage 3 proposal of JavaScript decorator metadata]
1033
+ at November 2023, the version of the [Babel] plugin [@babel/plugin-proposal-decorators]
1034
+ must be at least `7.24.0`.
1015
1035
 
1016
1036
  ### <span id="webpack">Bundling with [webpack]</span>
1017
1037
 
@@ -1031,7 +1051,7 @@ at least `7.23.0`.
1031
1051
  ],
1032
1052
  "plugins": [
1033
1053
  "@babel/plugin-transform-runtime",
1034
- ["@babel/plugin-proposal-decorators", { "version": "2023-05" }],
1054
+ ["@babel/plugin-proposal-decorators", { "version": "2023-11" }],
1035
1055
  "@babel/plugin-transform-class-properties"
1036
1056
  ]
1037
1057
  }
@@ -1055,7 +1075,7 @@ at least `7.23.0`.
1055
1075
  ],
1056
1076
  "plugins": [
1057
1077
  "@babel/plugin-transform-runtime",
1058
- ["@babel/plugin-proposal-decorators", { "version": "2023-05" }],
1078
+ ["@babel/plugin-proposal-decorators", { "version": "2023-11" }],
1059
1079
  "@babel/plugin-transform-class-properties"
1060
1080
  ]
1061
1081
  }
@@ -1154,3 +1174,608 @@ See the [LICENSE](LICENSE) file for more details.
1154
1174
  [vite-plugin-vue]: https://www.npmjs.com/package/@vitejs/plugin-vue
1155
1175
  [vite-plugin-babel]: https://www.npmjs.com/package/vite-plugin-babel
1156
1176
  [our version of vite-plugin-babel]: https://npmjs.com/package/@qubit-ltd/vite-plugin-babel
1177
+
1178
+ ## Front-end and Back-end Data Exchange Example
1179
+
1180
+ In real-world applications, data exchange between front-end and back-end is a common scenario. The following example demonstrates how to use this library to process data from RESTful APIs and send it back to the server.
1181
+
1182
+ ### Complete Example
1183
+
1184
+ Suppose we have an e-commerce application that needs to handle order data. The back-end API returns data in snake_case naming convention (e.g., `order_id`), while the front-end code uses camelCase (e.g., `orderId`). Additionally, order IDs use Java's Long type (which may exceed JavaScript's safe integer range).
1185
+
1186
+ First, let's define our domain models:
1187
+
1188
+ ```javascript
1189
+ import { Model, Type, ElementType, Normalizable, Validatable, NonEmpty } from '@qubit-ltd/common-decorator';
1190
+
1191
+ // Configure default options for naming style conversion
1192
+ DefaultOptions.set('assign', {
1193
+ normalize: true,
1194
+ convertNaming: true,
1195
+ sourceNamingStyle: 'LOWER_UNDERSCORE', // JSON data format from back-end
1196
+ targetNamingStyle: 'LOWER_CAMEL' // Format used in front-end
1197
+ });
1198
+
1199
+ DefaultOptions.set('toJSON', {
1200
+ normalize: true,
1201
+ removeEmptyFields: true, // Automatically remove empty fields
1202
+ convertNaming: true,
1203
+ sourceNamingStyle: 'LOWER_CAMEL', // Format used in front-end
1204
+ targetNamingStyle: 'LOWER_UNDERSCORE' // Format to send to back-end
1205
+ });
1206
+
1207
+ // Define the OrderItem model
1208
+ @Model
1209
+ class OrderItem {
1210
+ constructor() {
1211
+ this.id = null; // Java Long type, automatically converted to BigInt
1212
+ this.productId = null; // Front-end uses camelCase, corresponding to product_id in back-end
1213
+ this.productName = '';
1214
+ this.quantity = 0;
1215
+ this.unitPrice = 0;
1216
+ }
1217
+
1218
+ @Normalizable
1219
+ @Validatable
1220
+ @Type(String)
1221
+ get productName() {
1222
+ return this._productName;
1223
+ }
1224
+
1225
+ set productName(value) {
1226
+ this._productName = value;
1227
+ }
1228
+
1229
+ @Normalizable
1230
+ @Validatable
1231
+ @Type(Number)
1232
+ get quantity() {
1233
+ return this._quantity;
1234
+ }
1235
+
1236
+ set quantity(value) {
1237
+ this._quantity = value;
1238
+ }
1239
+
1240
+ @Normalizable
1241
+ @Validatable
1242
+ @Type(Number)
1243
+ get unitPrice() {
1244
+ return this._unitPrice;
1245
+ }
1246
+
1247
+ set unitPrice(value) {
1248
+ this._unitPrice = value;
1249
+ }
1250
+
1251
+ // Calculate total price
1252
+ getTotalPrice() {
1253
+ return this.quantity * this.unitPrice;
1254
+ }
1255
+ }
1256
+
1257
+ // Define the Order model
1258
+ @Model
1259
+ class Order {
1260
+ constructor() {
1261
+ this.id = null; // Java Long type, automatically converted to BigInt
1262
+ this.orderNumber = ''; // Front-end uses camelCase, corresponding to order_number in back-end
1263
+ this.customerId = null;
1264
+ this.customerName = '';
1265
+ this.orderDate = null;
1266
+ this.orderItems = []; // Array of order items
1267
+ this.totalAmount = 0;
1268
+ this.status = '';
1269
+ this.note = ''; // Optional field, empty values will be removed when sent to back-end
1270
+ }
1271
+
1272
+ @Normalizable
1273
+ @Validatable
1274
+ @NonEmpty
1275
+ @Type(String)
1276
+ get orderNumber() {
1277
+ return this._orderNumber;
1278
+ }
1279
+
1280
+ set orderNumber(value) {
1281
+ this._orderNumber = value;
1282
+ }
1283
+
1284
+ @Normalizable
1285
+ @Validatable
1286
+ @Type(String)
1287
+ get customerName() {
1288
+ return this._customerName;
1289
+ }
1290
+
1291
+ set customerName(value) {
1292
+ this._customerName = value;
1293
+ }
1294
+
1295
+ @Normalizable
1296
+ @Validatable
1297
+ @Type(Date)
1298
+ get orderDate() {
1299
+ return this._orderDate;
1300
+ }
1301
+
1302
+ set orderDate(value) {
1303
+ this._orderDate = value;
1304
+ }
1305
+
1306
+ @Normalizable
1307
+ @Validatable
1308
+ @ElementType(OrderItem)
1309
+ get orderItems() {
1310
+ return this._orderItems;
1311
+ }
1312
+
1313
+ set orderItems(value) {
1314
+ this._orderItems = value;
1315
+ }
1316
+ }
1317
+
1318
+ // Example usage - Fetching data from back-end
1319
+ async function fetchOrder(orderId) {
1320
+ try {
1321
+ // Assume this is the response from a back-end API
1322
+ const response = await fetch(`/api/orders/${orderId}`);
1323
+ const data = await response.json();
1324
+
1325
+ // Sample data (in snake_case naming style)
1326
+ // {
1327
+ // "id": "9223372036854775807", // Note: Max value of Java Long, exceeds JS Number safe range
1328
+ // "order_number": "ORD-2023-001",
1329
+ // "customer_id": "5678",
1330
+ // "customer_name": "John Doe",
1331
+ // "order_date": "2023-08-15T14:30:00.000Z",
1332
+ // "order_items": [
1333
+ // {
1334
+ // "id": "8345678912345678901", // Another large integer
1335
+ // "product_id": "101",
1336
+ // "product_name": "Laptop",
1337
+ // "quantity": 1,
1338
+ // "unit_price": 999.99
1339
+ // },
1340
+ // {
1341
+ // "id": "8345678912345678902",
1342
+ // "product_id": "202",
1343
+ // "product_name": "Wireless Mouse",
1344
+ // "quantity": 2,
1345
+ // "unit_price": 29.99
1346
+ // }
1347
+ // ],
1348
+ // "total_amount": 1059.97,
1349
+ // "status": "PENDING",
1350
+ // "note": null
1351
+ // }
1352
+
1353
+ // Create domain object using Order.create(), automatically handling naming style conversion and large integers
1354
+ const order = Order.create(data);
1355
+
1356
+ console.log(order.id); // Output: 9223372036854775807n (BigInt type)
1357
+ console.log(order.orderNumber); // Output: "ORD-2023-001" (converted to camelCase)
1358
+ console.log(order.orderItems[0].productName); // Output: "Laptop"
1359
+
1360
+ // Validation and normalization
1361
+ order.normalize();
1362
+ const validationResult = order.validate();
1363
+ if (!validationResult.valid) {
1364
+ console.error('Order data validation failed:', validationResult.message);
1365
+ }
1366
+
1367
+ return order;
1368
+ } catch (error) {
1369
+ console.error('Failed to fetch order:', error);
1370
+ throw error;
1371
+ }
1372
+ }
1373
+
1374
+ // Sending data back to the back-end
1375
+ async function updateOrder(order) {
1376
+ try {
1377
+ // Use toJSON to convert domain object to plain JavaScript object
1378
+ // Automatically handles: 1. Naming style conversion 2. Empty field removal 3. BigInt conversion
1379
+ const orderData = order.toJSON();
1380
+
1381
+ // Example of converted data format:
1382
+ // {
1383
+ // "id": "9223372036854775807", // BigInt converted to string, without 'n' suffix
1384
+ // "order_number": "ORD-2023-001",
1385
+ // "customer_id": "5678",
1386
+ // "customer_name": "John Doe",
1387
+ // "order_date": "2023-08-15T14:30:00.000Z",
1388
+ // "order_items": [
1389
+ // {
1390
+ // "id": "8345678912345678901",
1391
+ // "product_id": "101",
1392
+ // "product_name": "Laptop",
1393
+ // "quantity": 1,
1394
+ // "unit_price": 999.99
1395
+ // },
1396
+ // // ...other order items
1397
+ // ],
1398
+ // "total_amount": 1059.97,
1399
+ // "status": "PENDING"
1400
+ // // Note: 'note' field was null and has been automatically removed
1401
+ // }
1402
+
1403
+ const response = await fetch(`/api/orders/${order.id}`, {
1404
+ method: 'PUT',
1405
+ headers: {
1406
+ 'Content-Type': 'application/json'
1407
+ },
1408
+ body: JSON.stringify(orderData) // No additional processing needed, already in the right format
1409
+ });
1410
+
1411
+ if (!response.ok) {
1412
+ throw new Error(`Failed to update order: ${response.status}`);
1413
+ }
1414
+
1415
+ return await response.json();
1416
+ } catch (error) {
1417
+ console.error('Failed to update order:', error);
1418
+ throw error;
1419
+ }
1420
+ }
1421
+
1422
+ // Example usage
1423
+ async function example() {
1424
+ // Fetch order data and modify it
1425
+ const order = await fetchOrder('123');
1426
+ order.status = 'COMPLETED';
1427
+ order.note = ''; // Empty string, will be removed when sent to back-end
1428
+
1429
+ // Add a new item to the order
1430
+ const newItem = new OrderItem();
1431
+ newItem.productId = '303';
1432
+ newItem.productName = 'Headphones';
1433
+ newItem.quantity = 1;
1434
+ newItem.unitPrice = 79.99;
1435
+ order.orderItems.push(newItem);
1436
+
1437
+ // Recalculate total amount
1438
+ order.totalAmount = order.orderItems.reduce(
1439
+ (sum, item) => sum + item.getTotalPrice(), 0
1440
+ );
1441
+
1442
+ // Send the updated order back to the server
1443
+ await updateOrder(order);
1444
+ }
1445
+
1446
+ ```
1447
+
1448
+ ### Key Points
1449
+
1450
+ 1. **Automatic Naming Style Conversion**:
1451
+ - Data from the back-end uses snake_case format (e.g., `order_number`)
1452
+ - Front-end domain objects use camelCase format (e.g., `orderNumber`)
1453
+ - Through `DefaultOptions.set('assign', {...})` configuration, `Model.create()` and `model.assign()` automatically convert naming styles
1454
+ - Through `DefaultOptions.set('toJSON', {...})` configuration, `model.toJSON()` automatically converts camelCase back to snake_case
1455
+
1456
+ 2. **Large Integer Handling**:
1457
+ - Back-end uses Java Long type IDs (e.g., `9223372036854775807`) that exceed JavaScript's safe integer range
1458
+ - `Model.create()` and `model.assign()` automatically convert these large integers to JavaScript's BigInt type
1459
+ - `model.toJSON()` automatically converts BigInt to the correct JSON format (without the 'n' suffix)
1460
+
1461
+ 3. **Empty Field Handling**:
1462
+ - With `removeEmptyFields: true` configuration, `model.toJSON()` automatically removes null, undefined, and empty string properties
1463
+ - In the example, the 'note' field with null or empty string value won't be sent to the back-end
1464
+
1465
+ 4. **Type Conversion and Validation**:
1466
+ - Use `@Type` and `@ElementType` decorators to ensure type safety
1467
+ - Use `model.normalize()` for data normalization
1468
+ - Use `model.validate()` to validate data integrity
1469
+
1470
+ This complete example demonstrates how to use the features of this library to easily handle various challenges in front-end and back-end data exchange in real-world applications.
1471
+
1472
+ ## Advanced Usage
1473
+
1474
+ ### Combining Multiple Decorators
1475
+
1476
+ You can combine multiple decorators to add rich functionality to your classes:
1477
+
1478
+ ```javascript
1479
+ import {
1480
+ Model,
1481
+ Type,
1482
+ ElementType,
1483
+ Normalizable,
1484
+ Validatable,
1485
+ NonEmpty
1486
+ } from '@qubit-ltd/common-decorator';
1487
+
1488
+ @Model
1489
+ class Product {
1490
+ constructor() {
1491
+ this.id = null;
1492
+ this.name = '';
1493
+ this.price = 0;
1494
+ this.tags = [];
1495
+ this.createdAt = null;
1496
+ }
1497
+
1498
+ @NonEmpty
1499
+ @Validatable
1500
+ @Normalizable
1501
+ @Type(String)
1502
+ get name() {
1503
+ return this._name;
1504
+ }
1505
+
1506
+ set name(value) {
1507
+ this._name = value;
1508
+ }
1509
+
1510
+ @Validatable
1511
+ @Normalizable
1512
+ @Type(Number)
1513
+ get price() {
1514
+ return this._price;
1515
+ }
1516
+
1517
+ set price(value) {
1518
+ this._price = value;
1519
+ }
1520
+
1521
+ @Normalizable
1522
+ @ElementType(String)
1523
+ get tags() {
1524
+ return this._tags;
1525
+ }
1526
+
1527
+ set tags(value) {
1528
+ this._tags = value;
1529
+ }
1530
+ }
1531
+
1532
+ // Usage
1533
+ const product = new Product();
1534
+ product.assign({
1535
+ name: ' Product Name ',
1536
+ price: '99.99',
1537
+ tags: ['tag1', 2, 'tag3']
1538
+ });
1539
+
1540
+ // After normalization, product.name will be trimmed,
1541
+ // product.price will be a Number,
1542
+ // and all elements in product.tags will be strings
1543
+ product.normalize();
1544
+
1545
+ console.log(product.validate()); // Checks if name is not empty and all types match
1546
+ ```
1547
+
1548
+ ### Custom Validation and Normalization
1549
+
1550
+ You can implement custom validation and normalization logic:
1551
+
1552
+ ```javascript
1553
+ import { Model, Normalizable, Validatable } from '@qubit-ltd/common-decorator';
1554
+
1555
+ @Model
1556
+ class EmailSubscription {
1557
+ constructor() {
1558
+ this.email = '';
1559
+ this.subscribed = false;
1560
+ }
1561
+
1562
+ @Normalizable((value) => {
1563
+ // Custom normalizer that converts email to lowercase and trims whitespace
1564
+ return typeof value === 'string' ? value.toLowerCase().trim() : value;
1565
+ })
1566
+ @Validatable((value) => {
1567
+ // Custom validator that checks if email is valid
1568
+ if (typeof value !== 'string' || !value.includes('@')) {
1569
+ return {
1570
+ valid: false,
1571
+ message: 'Invalid email address',
1572
+ };
1573
+ }
1574
+ return { valid: true };
1575
+ })
1576
+ get email() {
1577
+ return this._email;
1578
+ }
1579
+
1580
+ set email(value) {
1581
+ this._email = value;
1582
+ }
1583
+ }
1584
+ ```
1585
+
1586
+ ### Working with DefaultOptions
1587
+
1588
+ You can configure default options for various operations:
1589
+
1590
+ ```javascript
1591
+ import { DefaultOptions, Model } from '@qubit-ltd/common-decorator';
1592
+
1593
+ // Configure default options for JSON serialization
1594
+ DefaultOptions.set('toJSON', {
1595
+ normalize: true,
1596
+ removeEmptyFields: true,
1597
+ convertNaming: true,
1598
+ sourceNamingStyle: 'LOWER_CAMEL',
1599
+ targetNamingStyle: 'LOWER_UNDERSCORE'
1600
+ });
1601
+
1602
+ @Model
1603
+ class Order {
1604
+ constructor() {
1605
+ this.orderId = null;
1606
+ this.customerName = '';
1607
+ this.orderItems = [];
1608
+ }
1609
+ }
1610
+
1611
+ const order = new Order();
1612
+ order.assign({
1613
+ orderId: '12345',
1614
+ customerName: 'John Doe',
1615
+ orderItems: [
1616
+ { itemId: 1, name: 'Product 1', quantity: 2 }
1617
+ ]
1618
+ });
1619
+
1620
+ // Will use the configured default options for serialization
1621
+ const json = order.toJsonString();
1622
+ console.log(json);
1623
+ // Output will use lower_underscore naming:
1624
+ // {"order_id":"12345","customer_name":"John Doe","order_items":[{"item_id":1,"name":"Product 1","quantity":2}]}
1625
+ ```
1626
+
1627
+ ## Integration Examples
1628
+
1629
+ ### Using with Vue.js
1630
+
1631
+ ```javascript
1632
+ import { Model, Normalizable, Validatable } from '@qubit-ltd/common-decorator';
1633
+ import { defineComponent, ref } from 'vue';
1634
+
1635
+ @Model
1636
+ class UserProfile {
1637
+ constructor() {
1638
+ this.username = '';
1639
+ this.email = '';
1640
+ this.bio = '';
1641
+ }
1642
+
1643
+ @Normalizable
1644
+ @Validatable
1645
+ get username() {
1646
+ return this._username;
1647
+ }
1648
+
1649
+ set username(value) {
1650
+ this._username = value;
1651
+ }
1652
+
1653
+ // Other getters and setters...
1654
+ }
1655
+
1656
+ export default defineComponent({
1657
+ setup() {
1658
+ const profile = ref(new UserProfile());
1659
+
1660
+ const updateProfile = (formData) => {
1661
+ profile.value.assign(formData);
1662
+ profile.value.normalize();
1663
+ const validation = profile.value.validate();
1664
+
1665
+ if (validation.valid) {
1666
+ // Save profile
1667
+ } else {
1668
+ // Handle validation errors
1669
+ console.error(validation.message);
1670
+ }
1671
+ };
1672
+
1673
+ return {
1674
+ profile,
1675
+ updateProfile
1676
+ };
1677
+ }
1678
+ });
1679
+ ```
1680
+
1681
+ ### Using with Express.js
1682
+
1683
+ ```javascript
1684
+ import express from 'express';
1685
+ import { Model, Normalizable, Validatable, NonEmpty } from '@qubit-ltd/common-decorator';
1686
+
1687
+ const app = express();
1688
+ app.use(express.json());
1689
+
1690
+ @Model
1691
+ class NewUserRequest {
1692
+ constructor() {
1693
+ this.username = '';
1694
+ this.email = '';
1695
+ this.password = '';
1696
+ }
1697
+
1698
+ @NonEmpty
1699
+ @Normalizable
1700
+ @Validatable
1701
+ get username() {
1702
+ return this._username;
1703
+ }
1704
+
1705
+ set username(value) {
1706
+ this._username = value;
1707
+ }
1708
+
1709
+ // Other getters and setters...
1710
+ }
1711
+
1712
+ app.post('/api/users', (req, res) => {
1713
+ try {
1714
+ const userRequest = NewUserRequest.create(req.body);
1715
+ userRequest.normalize();
1716
+ const validation = userRequest.validate();
1717
+
1718
+ if (!validation.valid) {
1719
+ return res.status(400).json({ error: validation.message });
1720
+ }
1721
+
1722
+ // Create user in database
1723
+ return res.status(201).json({ message: 'User created successfully' });
1724
+ } catch (error) {
1725
+ return res.status(500).json({ error: error.message });
1726
+ }
1727
+ });
1728
+
1729
+ app.listen(3000, () => {
1730
+ console.log('Server is running on port 3000');
1731
+ });
1732
+ ```
1733
+
1734
+ ## Compatibility and Requirements
1735
+
1736
+ - **Node.js**: 14.x or higher
1737
+ - **ECMAScript**: ES2022 or higher
1738
+ - **Decorator Support**: Requires babel configuration for Stage 3 decorators
1739
+ - **Browser Support**: Modern browsers with ES6+ support
1740
+
1741
+ ## Best Practices
1742
+
1743
+ ### Project Structure
1744
+
1745
+ When using this library, we recommend organizing your domain models in a structured way:
1746
+
1747
+ ```
1748
+ src/
1749
+ ├── models/
1750
+ │ ├── base/
1751
+ │ │ └── BaseModel.js
1752
+ │ ├── User.js
1753
+ │ ├── Product.js
1754
+ │ └── Order.js
1755
+ ├── enums/
1756
+ │ ├── Status.js
1757
+ │ └── Role.js
1758
+ └── app.js
1759
+ ```
1760
+
1761
+ ### Performance Considerations
1762
+
1763
+ - Use `normalize()` only when necessary, not on every operation
1764
+ - Consider caching validation results for frequently accessed objects
1765
+ - For large collections, use `createArray()` method instead of mapping each item
1766
+
1767
+ ### Type Safety
1768
+
1769
+ While JavaScript is dynamically typed, this library provides several ways to ensure type safety:
1770
+
1771
+ 1. Use the `@Type` decorator to enforce type checking for properties
1772
+ 2. Use the `@ElementType` decorator for arrays
1773
+ 3. Enable normalization to automatically convert values to the correct type
1774
+
1775
+ ### Memory Management
1776
+
1777
+ When working with large object graphs:
1778
+
1779
+ 1. Avoid deep cloning unnecessarily
1780
+ 2. Use the `removeEmptyFields` option in `toJSON` for large objects
1781
+ 3. Be cautious with circular references when serializing objects