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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Serializable } from "../classes/Serializable.js";
|
|
2
|
+
import { fromJSON } from "./FromJSON.js";
|
|
3
|
+
import { onWrongType } from "./OnWrongType.js";
|
|
4
|
+
/**
|
|
5
|
+
* Deserializes a single property value from JSON data based on accepted type definitions.
|
|
6
|
+
* This function iterates through accepted types and attempts to match and convert the JSON value
|
|
7
|
+
* to the appropriate TypeScript type. Supports primitives, arrays, dates, and complex object types.
|
|
8
|
+
*
|
|
9
|
+
* @param {object} obj - The object instance to which the property belongs
|
|
10
|
+
* @param {string} prop - The name of the property being deserialized
|
|
11
|
+
* @param {AcceptedTypes[]} acceptedTypes - Array of type constructors or type definitions that the property can accept
|
|
12
|
+
* @param {unknown} jsonValue - The raw JSON value to deserialize and convert
|
|
13
|
+
* @param {Partial<SerializationSettings>} [settings] - Optional settings to customize deserialization behavior
|
|
14
|
+
* @returns {unknown} The deserialized and typed value, or the original property value if no type match is found
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Supported type conversions:
|
|
18
|
+
* - `null` - Preserves null values
|
|
19
|
+
* - `undefined` (void 0) - For deep copy operations
|
|
20
|
+
* - `Boolean` - Converts boolean values and Boolean objects
|
|
21
|
+
* - `Number` - Converts numeric values and Number objects
|
|
22
|
+
* - `String` - Converts string values and String objects
|
|
23
|
+
* - `Object` - Converts plain objects
|
|
24
|
+
* - `Date` - Parses ISO strings, Date objects, and validates date values
|
|
25
|
+
* - `Array` - Recursively deserializes array elements
|
|
26
|
+
* - `Serializable` subclasses - Creates instances and calls fromJSON
|
|
27
|
+
* - Custom classes - Creates instances and applies fromJSON for non-Serializable classes
|
|
28
|
+
* - Instance checks - Validates existing instances of specific classes
|
|
29
|
+
*
|
|
30
|
+
* If no type matches, calls `onWrongType` error handler and returns the original property value.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const acceptedTypes = [String, Number];
|
|
35
|
+
* const result = deserializeProperty(obj, "age", acceptedTypes, "30");
|
|
36
|
+
* // Returns the string "30" since String is checked first
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
// eslint-disable-next-line max-lines-per-function, max-statements, complexity
|
|
40
|
+
export const deserializeProperty = (obj, prop, acceptedTypes, jsonValue, settings
|
|
41
|
+
// eslint-disable-next-line max-params
|
|
42
|
+
) => {
|
|
43
|
+
for (const acceptedType of acceptedTypes) { // Type Symbol is not a property
|
|
44
|
+
if ( // Null
|
|
45
|
+
acceptedType === null &&
|
|
46
|
+
jsonValue === null) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
else if ( // Void, for deep copy classes only, JSON doesn't have a void type
|
|
50
|
+
acceptedType === void 0 &&
|
|
51
|
+
jsonValue === void 0) {
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
else if ( // Boolean, Boolean
|
|
55
|
+
acceptedType === Boolean &&
|
|
56
|
+
(typeof jsonValue === "boolean" || jsonValue instanceof Boolean)) {
|
|
57
|
+
return Boolean(jsonValue);
|
|
58
|
+
}
|
|
59
|
+
else if ( // Number, Number
|
|
60
|
+
acceptedType === Number &&
|
|
61
|
+
(typeof jsonValue === "number" || jsonValue instanceof Number)) {
|
|
62
|
+
return Number(jsonValue);
|
|
63
|
+
}
|
|
64
|
+
else if ( // String, String
|
|
65
|
+
acceptedType === String &&
|
|
66
|
+
(typeof jsonValue === "string" || jsonValue instanceof String)) {
|
|
67
|
+
return String(jsonValue);
|
|
68
|
+
}
|
|
69
|
+
else if ( // Object, Object
|
|
70
|
+
acceptedType === Object &&
|
|
71
|
+
(typeof jsonValue === "object")) {
|
|
72
|
+
return Object(jsonValue);
|
|
73
|
+
}
|
|
74
|
+
else if ( // Date
|
|
75
|
+
acceptedType === Date &&
|
|
76
|
+
(typeof jsonValue === "string" || jsonValue instanceof String || jsonValue instanceof Date)) {
|
|
77
|
+
// 0 year, 0 month, 0 days, 0 hours, 0 minutes, 0 seconds
|
|
78
|
+
let unicodeTime = new Date("0000-01-01T00:00:00.000").getTime();
|
|
79
|
+
if (typeof jsonValue === "string") {
|
|
80
|
+
unicodeTime = Date.parse(jsonValue);
|
|
81
|
+
}
|
|
82
|
+
else if (jsonValue instanceof String) {
|
|
83
|
+
unicodeTime = Date.parse(String(jsonValue));
|
|
84
|
+
}
|
|
85
|
+
else if (jsonValue instanceof Date) {
|
|
86
|
+
unicodeTime = jsonValue.getTime();
|
|
87
|
+
}
|
|
88
|
+
if (isNaN(unicodeTime)) { // Preserve invalid time
|
|
89
|
+
if (obj instanceof Serializable) {
|
|
90
|
+
obj.onWrongType(prop, "is an invalid date", jsonValue);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
onWrongType(obj, prop, "is an invalid date", jsonValue);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return new Date(unicodeTime);
|
|
97
|
+
}
|
|
98
|
+
else if ( // Array
|
|
99
|
+
Array.isArray(acceptedType) &&
|
|
100
|
+
Array.isArray(jsonValue)) {
|
|
101
|
+
if (acceptedType[0] === void 0) {
|
|
102
|
+
if (obj instanceof Serializable) {
|
|
103
|
+
obj.onWrongType(prop, "invalid type", jsonValue);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
onWrongType(obj, prop, "invalid type", jsonValue);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return jsonValue.map((arrayValue) => deserializeProperty(obj, prop, acceptedType, arrayValue, settings));
|
|
110
|
+
}
|
|
111
|
+
else if ( // Serializable
|
|
112
|
+
acceptedType !== null &&
|
|
113
|
+
acceptedType !== void 0 &&
|
|
114
|
+
!Array.isArray(acceptedType) &&
|
|
115
|
+
(acceptedType.prototype instanceof Serializable ||
|
|
116
|
+
acceptedType instanceof Function) &&
|
|
117
|
+
jsonValue !== null &&
|
|
118
|
+
jsonValue !== void 0 &&
|
|
119
|
+
typeof jsonValue === "object" && !Array.isArray(jsonValue)) {
|
|
120
|
+
if (acceptedType.prototype instanceof Serializable) {
|
|
121
|
+
const TypeConstructor = acceptedType;
|
|
122
|
+
return new TypeConstructor().fromJSON(jsonValue, settings);
|
|
123
|
+
}
|
|
124
|
+
// Class without Serializable base class
|
|
125
|
+
const TypeConstructor = acceptedType;
|
|
126
|
+
return fromJSON(new TypeConstructor(), jsonValue, settings);
|
|
127
|
+
}
|
|
128
|
+
else if ( // Instance any other class, not Serializable, for parsing from other class instances
|
|
129
|
+
acceptedType instanceof Function &&
|
|
130
|
+
jsonValue instanceof acceptedType) {
|
|
131
|
+
return jsonValue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Process incorrect type and return default value
|
|
135
|
+
if (obj instanceof Serializable) {
|
|
136
|
+
obj.onWrongType(prop, "is invalid", jsonValue);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
onWrongType(obj, prop, "is invalid", jsonValue);
|
|
140
|
+
}
|
|
141
|
+
return Reflect.get(obj, prop);
|
|
142
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Serializable } from "../classes/Serializable.js";
|
|
2
|
+
import { SerializationSettings } from "../models/SerializationSettings.js";
|
|
3
|
+
/**
|
|
4
|
+
* Deserializes JSON data into an existing object instance, populating its properties.
|
|
5
|
+
* This function reads metadata from @JsonProperty decorators to determine property types
|
|
6
|
+
* and performs appropriate type conversions. It supports both Serializable and plain object instances.
|
|
7
|
+
*
|
|
8
|
+
* @template T - The type of object being deserialized (extends Serializable or plain object)
|
|
9
|
+
* @param {T} obj - The target object instance to populate with deserialized data
|
|
10
|
+
* @param {object} json - The JSON object containing source data for deserialization
|
|
11
|
+
* @param {Partial<SerializationSettings>} [settings] - Optional settings to customize deserialization behavior
|
|
12
|
+
* @returns {T} The same object instance with properties populated from JSON
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* The function performs the following steps:
|
|
16
|
+
* 1. Validates that json is an object (not null, array, or primitive)
|
|
17
|
+
* 2. Iterates through all properties of the target object
|
|
18
|
+
* 3. Resolves JSON property names using @JsonName decorators and naming strategies
|
|
19
|
+
* 4. Falls back to original property names for deep copy operations
|
|
20
|
+
* 5. Deserializes each property value according to its type metadata
|
|
21
|
+
* 6. Calls error handlers for invalid or missing data
|
|
22
|
+
*
|
|
23
|
+
* Property name resolution priority:
|
|
24
|
+
* - @JsonName decorator value
|
|
25
|
+
* - Naming strategy transformation (snake_case, camelCase, etc.)
|
|
26
|
+
* - Original property name (fallback for deep copy)
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* class User extends Serializable {
|
|
31
|
+
* @JsonProperty(String)
|
|
32
|
+
* name: string;
|
|
33
|
+
*
|
|
34
|
+
* @JsonProperty(Number)
|
|
35
|
+
* age: number;
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* const user = new User();
|
|
39
|
+
* fromJSON(user, { name: "John", age: 30 });
|
|
40
|
+
* console.log(user.name); // "John"
|
|
41
|
+
* console.log(user.age); // 30
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* // Using with plain objects (non-Serializable)
|
|
47
|
+
* class Product {
|
|
48
|
+
* @JsonProperty(String)
|
|
49
|
+
* title: string;
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* const product = new Product();
|
|
53
|
+
* fromJSON(product, { title: "Laptop" });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare const fromJSON: <T extends (Serializable | object)>(obj: T, json: object, settings?: Partial<SerializationSettings>) => T;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
|
+
/* eslint-disable no-prototype-builtins */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
4
|
+
import { Serializable } from "../classes/Serializable.js";
|
|
5
|
+
import { deserializeProperty } from "./DeserializeProperty.js";
|
|
6
|
+
import { getPropertyName } from "./GetPropertyName.js";
|
|
7
|
+
import { onWrongType } from "./OnWrongType.js";
|
|
8
|
+
/**
|
|
9
|
+
* Deserializes JSON data into an existing object instance, populating its properties.
|
|
10
|
+
* This function reads metadata from @JsonProperty decorators to determine property types
|
|
11
|
+
* and performs appropriate type conversions. It supports both Serializable and plain object instances.
|
|
12
|
+
*
|
|
13
|
+
* @template T - The type of object being deserialized (extends Serializable or plain object)
|
|
14
|
+
* @param {T} obj - The target object instance to populate with deserialized data
|
|
15
|
+
* @param {object} json - The JSON object containing source data for deserialization
|
|
16
|
+
* @param {Partial<SerializationSettings>} [settings] - Optional settings to customize deserialization behavior
|
|
17
|
+
* @returns {T} The same object instance with properties populated from JSON
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* The function performs the following steps:
|
|
21
|
+
* 1. Validates that json is an object (not null, array, or primitive)
|
|
22
|
+
* 2. Iterates through all properties of the target object
|
|
23
|
+
* 3. Resolves JSON property names using @JsonName decorators and naming strategies
|
|
24
|
+
* 4. Falls back to original property names for deep copy operations
|
|
25
|
+
* 5. Deserializes each property value according to its type metadata
|
|
26
|
+
* 6. Calls error handlers for invalid or missing data
|
|
27
|
+
*
|
|
28
|
+
* Property name resolution priority:
|
|
29
|
+
* - @JsonName decorator value
|
|
30
|
+
* - Naming strategy transformation (snake_case, camelCase, etc.)
|
|
31
|
+
* - Original property name (fallback for deep copy)
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* class User extends Serializable {
|
|
36
|
+
* @JsonProperty(String)
|
|
37
|
+
* name: string;
|
|
38
|
+
*
|
|
39
|
+
* @JsonProperty(Number)
|
|
40
|
+
* age: number;
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* const user = new User();
|
|
44
|
+
* fromJSON(user, { name: "John", age: 30 });
|
|
45
|
+
* console.log(user.name); // "John"
|
|
46
|
+
* console.log(user.age); // 30
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* // Using with plain objects (non-Serializable)
|
|
52
|
+
* class Product {
|
|
53
|
+
* @JsonProperty(String)
|
|
54
|
+
* title: string;
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* const product = new Product();
|
|
58
|
+
* fromJSON(product, { title: "Laptop" });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
// eslint-disable-next-line max-statements
|
|
62
|
+
export const fromJSON = (obj, json, settings) => {
|
|
63
|
+
const unknownJson = json;
|
|
64
|
+
if (unknownJson === null ||
|
|
65
|
+
Array.isArray(unknownJson) ||
|
|
66
|
+
typeof unknownJson !== "object") {
|
|
67
|
+
if (obj instanceof Serializable) {
|
|
68
|
+
obj.onWrongType(String(unknownJson), "is not an object", unknownJson);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
onWrongType(obj, String(unknownJson), "is not an object", unknownJson);
|
|
72
|
+
}
|
|
73
|
+
return obj;
|
|
74
|
+
}
|
|
75
|
+
// eslint-disable-next-line guard-for-in
|
|
76
|
+
for (const thisProp in obj) {
|
|
77
|
+
// Naming strategy and jsonName decorator
|
|
78
|
+
let jsonProp = obj instanceof Serializable ?
|
|
79
|
+
obj.getJsonPropertyName(thisProp, settings) :
|
|
80
|
+
getPropertyName(obj, thisProp, settings);
|
|
81
|
+
// For deep copy
|
|
82
|
+
if (!unknownJson?.hasOwnProperty(jsonProp) && unknownJson?.hasOwnProperty(thisProp)) {
|
|
83
|
+
jsonProp = thisProp;
|
|
84
|
+
}
|
|
85
|
+
if (unknownJson?.hasOwnProperty(jsonProp) &&
|
|
86
|
+
obj.hasOwnProperty(thisProp) &&
|
|
87
|
+
Reflect.hasMetadata("ts-serializable:jsonTypes", obj.constructor.prototype, thisProp)) {
|
|
88
|
+
const acceptedTypes = Reflect.getMetadata("ts-serializable:jsonTypes", obj.constructor.prototype, thisProp);
|
|
89
|
+
const jsonValue = Reflect.get(unknownJson, jsonProp);
|
|
90
|
+
const extractedValue = obj instanceof Serializable ?
|
|
91
|
+
obj.deserializeProperty(thisProp, acceptedTypes, jsonValue, settings) :
|
|
92
|
+
deserializeProperty(obj, thisProp, acceptedTypes, jsonValue, settings);
|
|
93
|
+
Reflect.set(obj, thisProp, extractedValue);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return obj;
|
|
97
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { SerializationSettings } from "../models/SerializationSettings.js";
|
|
2
|
+
/**
|
|
3
|
+
* Determines the final JSON property name based on decorators, settings, and naming strategies.
|
|
4
|
+
* This function applies transformations in the following priority order:
|
|
5
|
+
* 1. @JsonName decorator (highest priority)
|
|
6
|
+
* 2. Naming strategy from method-level settings parameter
|
|
7
|
+
* 3. Naming strategy from @JsonObject decorator on the class
|
|
8
|
+
* 4. Naming strategy from Serializable.defaultSettings
|
|
9
|
+
* 5. Original property name (no transformation)
|
|
10
|
+
*
|
|
11
|
+
* @param {object} obj - The object instance containing the property
|
|
12
|
+
* @param {string} property - The original property name as defined in the class
|
|
13
|
+
* @param {Partial<SerializationSettings>} [settings] - Optional settings that may include a naming strategy
|
|
14
|
+
* @returns {string} The transformed property name to use in JSON output
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // With @JsonName decorator
|
|
19
|
+
* class User {
|
|
20
|
+
* @JsonName("user_id")
|
|
21
|
+
* userId: string;
|
|
22
|
+
* }
|
|
23
|
+
* getPropertyName(user, "userId"); // Returns "user_id"
|
|
24
|
+
*
|
|
25
|
+
* // With snake_case naming strategy
|
|
26
|
+
* class Product {
|
|
27
|
+
* @JsonProperty()
|
|
28
|
+
* productName: string;
|
|
29
|
+
* }
|
|
30
|
+
* getPropertyName(product, "productName", { namingStrategy: new SnakeCaseNamingStrategy() });
|
|
31
|
+
* // Returns "product_name"
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* The @JsonName decorator always takes precedence over any naming strategy.
|
|
36
|
+
* Naming strategies can transform property names to conventions like snake_case, kebab-case, PascalCase, etc.
|
|
37
|
+
*/
|
|
38
|
+
export declare const getPropertyName: (obj: object, property: string, settings?: Partial<SerializationSettings>) => string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
3
|
+
import { Serializable } from "../classes/Serializable.js";
|
|
4
|
+
/**
|
|
5
|
+
* Determines the final JSON property name based on decorators, settings, and naming strategies.
|
|
6
|
+
* This function applies transformations in the following priority order:
|
|
7
|
+
* 1. @JsonName decorator (highest priority)
|
|
8
|
+
* 2. Naming strategy from method-level settings parameter
|
|
9
|
+
* 3. Naming strategy from @JsonObject decorator on the class
|
|
10
|
+
* 4. Naming strategy from Serializable.defaultSettings
|
|
11
|
+
* 5. Original property name (no transformation)
|
|
12
|
+
*
|
|
13
|
+
* @param {object} obj - The object instance containing the property
|
|
14
|
+
* @param {string} property - The original property name as defined in the class
|
|
15
|
+
* @param {Partial<SerializationSettings>} [settings] - Optional settings that may include a naming strategy
|
|
16
|
+
* @returns {string} The transformed property name to use in JSON output
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // With @JsonName decorator
|
|
21
|
+
* class User {
|
|
22
|
+
* @JsonName("user_id")
|
|
23
|
+
* userId: string;
|
|
24
|
+
* }
|
|
25
|
+
* getPropertyName(user, "userId"); // Returns "user_id"
|
|
26
|
+
*
|
|
27
|
+
* // With snake_case naming strategy
|
|
28
|
+
* class Product {
|
|
29
|
+
* @JsonProperty()
|
|
30
|
+
* productName: string;
|
|
31
|
+
* }
|
|
32
|
+
* getPropertyName(product, "productName", { namingStrategy: new SnakeCaseNamingStrategy() });
|
|
33
|
+
* // Returns "product_name"
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* The @JsonName decorator always takes precedence over any naming strategy.
|
|
38
|
+
* Naming strategies can transform property names to conventions like snake_case, kebab-case, PascalCase, etc.
|
|
39
|
+
*/
|
|
40
|
+
export const getPropertyName = (obj, property, settings) => {
|
|
41
|
+
if (Reflect.hasMetadata("ts-serializable:jsonName", obj.constructor.prototype, property)) {
|
|
42
|
+
return Reflect.getMetadata("ts-serializable:jsonName", obj.constructor.prototype, property);
|
|
43
|
+
}
|
|
44
|
+
if (settings?.namingStrategy) {
|
|
45
|
+
return settings.namingStrategy.toJsonName(property);
|
|
46
|
+
}
|
|
47
|
+
if (Reflect.hasMetadata("ts-serializable:jsonObject", obj.constructor)) {
|
|
48
|
+
const objectSettings = Reflect.getMetadata("ts-serializable:jsonObject", obj.constructor);
|
|
49
|
+
return objectSettings.namingStrategy?.toJsonName(property) ?? property;
|
|
50
|
+
}
|
|
51
|
+
if (Serializable.defaultSettings.namingStrategy) {
|
|
52
|
+
const { namingStrategy } = Serializable.defaultSettings;
|
|
53
|
+
return namingStrategy.toJsonName(property);
|
|
54
|
+
}
|
|
55
|
+
return property;
|
|
56
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default error handler for type mismatch errors during deserialization.
|
|
3
|
+
* This function is called when a JSON value cannot be converted to any of the expected types
|
|
4
|
+
* for a property. By default, it logs an error to the console.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} self - The object instance being deserialized
|
|
7
|
+
* @param {string} prop - The name of the property that has a type mismatch
|
|
8
|
+
* @param {string} message - A descriptive error message explaining the type issue
|
|
9
|
+
* @param {unknown} jsonValue - The actual JSON value that caused the type mismatch
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* This function is used for objects that don't extend the Serializable class.
|
|
13
|
+
* For Serializable instances, the instance method `onWrongType` is called instead,
|
|
14
|
+
* which can be overridden for custom error handling.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // Called internally when type mismatch occurs:
|
|
19
|
+
* onWrongType(userObj, "age", "is invalid", "not-a-number");
|
|
20
|
+
* // Console output: "User.fromJSON: json.age is invalid: not-a-number"
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare const onWrongType: (self: object, prop: string, message: string, jsonValue: unknown) => void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default error handler for type mismatch errors during deserialization.
|
|
3
|
+
* This function is called when a JSON value cannot be converted to any of the expected types
|
|
4
|
+
* for a property. By default, it logs an error to the console.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} self - The object instance being deserialized
|
|
7
|
+
* @param {string} prop - The name of the property that has a type mismatch
|
|
8
|
+
* @param {string} message - A descriptive error message explaining the type issue
|
|
9
|
+
* @param {unknown} jsonValue - The actual JSON value that caused the type mismatch
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* This function is used for objects that don't extend the Serializable class.
|
|
13
|
+
* For Serializable instances, the instance method `onWrongType` is called instead,
|
|
14
|
+
* which can be overridden for custom error handling.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // Called internally when type mismatch occurs:
|
|
19
|
+
* onWrongType(userObj, "age", "is invalid", "not-a-number");
|
|
20
|
+
* // Console output: "User.fromJSON: json.age is invalid: not-a-number"
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
// eslint-disable-next-line max-params
|
|
24
|
+
export const onWrongType = (self, prop, message, jsonValue) => {
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
26
|
+
console.error(`${self.constructor.name}.fromJSON: json.${prop} ${message}:`, jsonValue);
|
|
27
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Serializable } from "../classes/Serializable.js";
|
|
2
|
+
/**
|
|
3
|
+
* Serializes an object or Serializable instance to a plain JavaScript object suitable for JSON conversion.
|
|
4
|
+
* This function iterates through all own properties and creates a new object with transformed property names,
|
|
5
|
+
* respecting @JsonIgnore decorators and naming strategies.
|
|
6
|
+
*
|
|
7
|
+
* @param {Serializable | object} obj - The object or Serializable instance to serialize
|
|
8
|
+
* @returns {Record<string, unknown>} A plain object with all serializable properties
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* - Symbol properties are automatically skipped
|
|
12
|
+
* - Properties marked with @JsonIgnore decorator are excluded
|
|
13
|
+
* - Property names are transformed according to @JsonName decorators and naming strategies
|
|
14
|
+
* - For Serializable instances, uses the instance's getJsonPropertyName method
|
|
15
|
+
* - For plain objects, uses the standalone getPropertyName function
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* class User extends Serializable {
|
|
20
|
+
* @JsonProperty()
|
|
21
|
+
* firstName: string = "John";
|
|
22
|
+
*
|
|
23
|
+
* @JsonProperty()
|
|
24
|
+
* lastName: string = "Doe";
|
|
25
|
+
*
|
|
26
|
+
* @JsonIgnore()
|
|
27
|
+
* password: string = "secret";
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* const user = new User();
|
|
31
|
+
* const json = toJSON(user);
|
|
32
|
+
* // Returns: { firstName: "John", lastName: "Doe" }
|
|
33
|
+
* // Note: password is excluded due to @JsonIgnore
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare const toJSON: (obj: Serializable | object) => Record<string, unknown>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
|
+
/* eslint-disable no-prototype-builtins */
|
|
3
|
+
import { Serializable } from "../classes/Serializable.js";
|
|
4
|
+
import { getPropertyName } from "./GetPropertyName.js";
|
|
5
|
+
/**
|
|
6
|
+
* Serializes an object or Serializable instance to a plain JavaScript object suitable for JSON conversion.
|
|
7
|
+
* This function iterates through all own properties and creates a new object with transformed property names,
|
|
8
|
+
* respecting @JsonIgnore decorators and naming strategies.
|
|
9
|
+
*
|
|
10
|
+
* @param {Serializable | object} obj - The object or Serializable instance to serialize
|
|
11
|
+
* @returns {Record<string, unknown>} A plain object with all serializable properties
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* - Symbol properties are automatically skipped
|
|
15
|
+
* - Properties marked with @JsonIgnore decorator are excluded
|
|
16
|
+
* - Property names are transformed according to @JsonName decorators and naming strategies
|
|
17
|
+
* - For Serializable instances, uses the instance's getJsonPropertyName method
|
|
18
|
+
* - For plain objects, uses the standalone getPropertyName function
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* class User extends Serializable {
|
|
23
|
+
* @JsonProperty()
|
|
24
|
+
* firstName: string = "John";
|
|
25
|
+
*
|
|
26
|
+
* @JsonProperty()
|
|
27
|
+
* lastName: string = "Doe";
|
|
28
|
+
*
|
|
29
|
+
* @JsonIgnore()
|
|
30
|
+
* password: string = "secret";
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* const user = new User();
|
|
34
|
+
* const json = toJSON(user);
|
|
35
|
+
* // Returns: { firstName: "John", lastName: "Doe" }
|
|
36
|
+
* // Note: password is excluded due to @JsonIgnore
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export const toJSON = (obj) => {
|
|
40
|
+
const toJson = {};
|
|
41
|
+
const keys = Reflect.ownKeys(obj);
|
|
42
|
+
for (const key of keys) {
|
|
43
|
+
if (typeof key === "symbol") {
|
|
44
|
+
// eslint-disable-next-line no-continue
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (obj.hasOwnProperty(key)) {
|
|
48
|
+
if (Reflect.getMetadata("ts-serializable:jsonIgnore", obj.constructor.prototype, key) !== true) {
|
|
49
|
+
const toProp = obj instanceof Serializable ?
|
|
50
|
+
obj.getJsonPropertyName(key) :
|
|
51
|
+
getPropertyName(obj, key);
|
|
52
|
+
Reflect.set(toJson, toProp, Reflect.get(obj, key));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return toJson;
|
|
57
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -15,5 +15,9 @@ export { SnakeCaseNamingStrategy } from "./naming-strategies/SnakeCaseNamingStra
|
|
|
15
15
|
export { PascalCaseNamingStrategy } from "./naming-strategies/PascalCaseNamingStrategy.js";
|
|
16
16
|
export { KebabCaseNamingStrategy } from "./naming-strategies/KebabCaseNamingStrategy.js";
|
|
17
17
|
export { CamelCaseNamingStrategy } from "./naming-strategies/CamelCaseNamingStrategy.js";
|
|
18
|
-
export { classToFormData } from "./
|
|
19
|
-
export {
|
|
18
|
+
export { classToFormData } from "./functions/ClassToFormData.js";
|
|
19
|
+
export { deserializeProperty } from "./functions/DeserializeProperty.js";
|
|
20
|
+
export { fromJSON } from "./functions/FromJSON.js";
|
|
21
|
+
export { getPropertyName } from "./functions/GetPropertyName.js";
|
|
22
|
+
export { onWrongType } from "./functions/OnWrongType.js";
|
|
23
|
+
export { toJSON } from "./functions/ToJSON.js";
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,10 @@ export { SnakeCaseNamingStrategy } from "./naming-strategies/SnakeCaseNamingStra
|
|
|
18
18
|
export { PascalCaseNamingStrategy } from "./naming-strategies/PascalCaseNamingStrategy.js";
|
|
19
19
|
export { KebabCaseNamingStrategy } from "./naming-strategies/KebabCaseNamingStrategy.js";
|
|
20
20
|
export { CamelCaseNamingStrategy } from "./naming-strategies/CamelCaseNamingStrategy.js";
|
|
21
|
-
//
|
|
22
|
-
export { classToFormData } from "./
|
|
23
|
-
export {
|
|
21
|
+
// Functions
|
|
22
|
+
export { classToFormData } from "./functions/ClassToFormData.js";
|
|
23
|
+
export { deserializeProperty } from "./functions/DeserializeProperty.js";
|
|
24
|
+
export { fromJSON } from "./functions/FromJSON.js";
|
|
25
|
+
export { getPropertyName } from "./functions/GetPropertyName.js";
|
|
26
|
+
export { onWrongType } from "./functions/OnWrongType.js";
|
|
27
|
+
export { toJSON } from "./functions/ToJSON.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-serializable",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.2",
|
|
4
4
|
"author": "Eugene Labutin",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/LabEG/Serializable#readme",
|
|
@@ -34,17 +34,17 @@
|
|
|
34
34
|
"reflect-metadata": ">=0.1.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@commitlint/cli": "^
|
|
38
|
-
"@commitlint/config-conventional": "^
|
|
37
|
+
"@commitlint/cli": "^20.1.0",
|
|
38
|
+
"@commitlint/config-conventional": "^20.0.0",
|
|
39
39
|
"@favware/cliff-jumper": "^6.0.0",
|
|
40
40
|
"@labeg/code-style": "^6.5.0",
|
|
41
|
-
"@swc-node/register": "^1.
|
|
41
|
+
"@swc-node/register": "^1.11.1",
|
|
42
42
|
"@types/chai": "^5.2.2",
|
|
43
|
-
"chai": "^
|
|
43
|
+
"chai": "^6.2.0",
|
|
44
44
|
"husky": "^9.1.7",
|
|
45
|
-
"lint-staged": "^16.
|
|
45
|
+
"lint-staged": "^16.2.3",
|
|
46
46
|
"reflect-metadata": "^0.2.2",
|
|
47
|
-
"typescript": "^5.
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
48
|
},
|
|
49
49
|
"keywords": [
|
|
50
50
|
"serialization",
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Converts a class instance to FormData for use in AJAX forms.
|
|
3
|
-
*
|
|
4
|
-
* @param {object} obj - The class instance to convert.
|
|
5
|
-
* @param {string} [formPrefix] - Optional prefix for form property names.
|
|
6
|
-
* @param {FormData} [formData] - Optional existing FormData to update.
|
|
7
|
-
* @returns {FormData} - The resulting FormData object.
|
|
8
|
-
*/
|
|
9
|
-
export declare const classToFormData: (obj: object, formPrefix?: string, formData?: FormData) => FormData;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { SerializationSettings } from "../models/SerializationSettings.js";
|
|
2
|
-
/**
|
|
3
|
-
* Retrieves the correct property name for serialization, considering decorators and settings.
|
|
4
|
-
*
|
|
5
|
-
* @param {object} obj - The object containing the property.
|
|
6
|
-
* @param {string} property - The source name of the property.
|
|
7
|
-
* @param {Partial<SerializationSettings>} [settings] - Optional serialization settings.
|
|
8
|
-
* @returns {string} - The correct property name for serialization.
|
|
9
|
-
*/
|
|
10
|
-
export declare const getPropertyName: (obj: object, property: string, settings?: Partial<SerializationSettings>) => string;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-statements */
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
3
|
-
import { Serializable } from "../classes/Serializable.js";
|
|
4
|
-
/**
|
|
5
|
-
* Retrieves the correct property name for serialization, considering decorators and settings.
|
|
6
|
-
*
|
|
7
|
-
* @param {object} obj - The object containing the property.
|
|
8
|
-
* @param {string} property - The source name of the property.
|
|
9
|
-
* @param {Partial<SerializationSettings>} [settings] - Optional serialization settings.
|
|
10
|
-
* @returns {string} - The correct property name for serialization.
|
|
11
|
-
*/
|
|
12
|
-
export const getPropertyName = (obj, property, settings) => {
|
|
13
|
-
if (Reflect.hasMetadata("ts-serializable:jsonName", obj.constructor.prototype, property)) {
|
|
14
|
-
return Reflect.getMetadata("ts-serializable:jsonName", obj.constructor.prototype, property);
|
|
15
|
-
}
|
|
16
|
-
if (settings?.namingStrategy) {
|
|
17
|
-
return settings.namingStrategy.toJsonName(property);
|
|
18
|
-
}
|
|
19
|
-
if (Reflect.hasMetadata("ts-serializable:jsonObject", obj.constructor)) {
|
|
20
|
-
const objectSettings = Reflect.getMetadata("ts-serializable:jsonObject", obj.constructor);
|
|
21
|
-
return objectSettings.namingStrategy?.toJsonName(property) ?? property;
|
|
22
|
-
}
|
|
23
|
-
if (Serializable.defaultSettings.namingStrategy) {
|
|
24
|
-
const { namingStrategy } = Serializable.defaultSettings;
|
|
25
|
-
return namingStrategy.toJsonName(property);
|
|
26
|
-
}
|
|
27
|
-
return property;
|
|
28
|
-
};
|