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.
@@ -1,22 +1,35 @@
1
- /* eslint-disable max-lines */
2
- /* eslint-disable no-prototype-builtins */
3
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
4
- /* eslint-disable complexity */
5
- /* eslint-disable max-lines-per-function */
6
- /* eslint-disable max-statements */
7
1
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
2
+ import { deserializeProperty } from "../functions/DeserializeProperty.js";
8
3
  import { SerializationSettings } from "../models/SerializationSettings.js";
9
- import { classToFormData } from "../utils/ClassToFormData.js";
10
- import { getPropertyName } from "../utils/GetProperyName.js";
4
+ import { classToFormData } from "../functions/ClassToFormData.js";
5
+ import { getPropertyName } from "../functions/GetPropertyName.js";
6
+ import { fromJSON } from "../functions/FromJSON.js";
7
+ import { toJSON } from "../functions/ToJSON.js";
11
8
  /**
12
- * Class that helps you deserialize objects to classes.
9
+ * Base class that provides serialization and deserialization functionality for converting
10
+ * objects to and from JSON format. This class uses decorators to define how properties
11
+ * should be serialized and deserialized.
13
12
  *
14
13
  * @export
15
14
  * @class Serializable
15
+ * @example
16
+ * ```typescript
17
+ * class User extends Serializable {
18
+ * @JsonProperty()
19
+ * name: string;
20
+ *
21
+ * @JsonProperty()
22
+ * age: number;
23
+ * }
24
+ *
25
+ * const user = User.fromJSON({ name: "John", age: 30 });
26
+ * const json = user.toJSON();
27
+ * ```
16
28
  */
17
29
  export class Serializable {
18
30
  /**
19
- * Global settings for serialization and deserialization.
31
+ * Global default settings for all serialization and deserialization operations.
32
+ * These settings can be overridden per operation by passing custom settings.
20
33
  *
21
34
  * @static
22
35
  * @type {SerializationSettings}
@@ -24,245 +37,194 @@ export class Serializable {
24
37
  */
25
38
  static defaultSettings = new SerializationSettings();
26
39
  /**
27
- * Deserialize an object using a static method.
28
- *
29
- * Example:
30
- * const obj: MyObject = MyObject.fromJSON({...data});
40
+ * Creates a new instance of the class and deserializes JSON data into it.
41
+ * This is a convenient static method that combines instantiation and deserialization.
31
42
  *
32
43
  * @static
33
- * @param {object} json
34
- * @returns {object}
44
+ * @template T - The type of the Serializable class
45
+ * @param {object} json - The JSON object to deserialize
46
+ * @param {Partial<SerializationSettings>} [settings] - Optional settings to override default serialization behavior
47
+ * @returns {T} A new instance of the class with properties populated from the JSON
35
48
  * @memberof Serializable
49
+ * @example
50
+ * ```typescript
51
+ * const user = User.fromJSON({ name: "John", age: 30 });
52
+ * ```
36
53
  */
37
54
  static fromJSON(json, settings) {
38
55
  return new this().fromJSON(json, settings);
39
56
  }
40
57
  /**
41
- * Deserialize an object from a string using a static method.
42
- *
43
- * Example:
44
- * const obj: MyObject = MyObject.fromString("{...data}");
58
+ * Creates a new instance of the class and deserializes JSON string data into it.
59
+ * Automatically parses the JSON string before deserialization.
45
60
  *
46
61
  * @static
47
- * @param {string} str
48
- * @returns {object}
62
+ * @template T - The type of the Serializable class
63
+ * @param {string} str - The JSON string to parse and deserialize
64
+ * @param {Partial<SerializationSettings>} [settings] - Optional settings to override default serialization behavior
65
+ * @returns {T} A new instance of the class with properties populated from the parsed JSON
49
66
  * @memberof Serializable
67
+ * @throws {SyntaxError} If the string is not valid JSON
68
+ * @example
69
+ * ```typescript
70
+ * const user = User.fromString('{"name":"John","age":30}');
71
+ * ```
50
72
  */
51
73
  static fromString(str, settings) {
52
74
  return new this().fromJSON(JSON.parse(str), settings);
53
75
  }
54
76
  /**
55
- * Fill properties of the current model with data from a string.
56
- *
57
- * Example:
58
- * const obj: MyObject = new MyObject().fromString("{...data}");
77
+ * Populates the current instance's properties with data from a JSON object.
78
+ * Uses metadata from decorators to determine how to deserialize each property.
59
79
  *
60
- * @param {string} str
61
- * @returns {this}
80
+ * @param {object} json - The JSON object containing data to populate the instance
81
+ * @param {Partial<SerializationSettings>} [settings] - Optional settings to override default serialization behavior
82
+ * @returns {this} The current instance for method chaining
62
83
  * @memberof Serializable
84
+ * @example
85
+ * ```typescript
86
+ * const user = new User();
87
+ * user.fromJSON({ name: "John", age: 30 });
88
+ * ```
63
89
  */
64
- fromString(str, settings) {
65
- return this.fromJSON(JSON.parse(str), settings);
90
+ fromJSON(json, settings) {
91
+ return fromJSON(this, json, settings);
66
92
  }
67
93
  /**
68
- * Fill properties of the current model with data from JSON.
69
- *
70
- * Example:
71
- * const obj: MyObject = new MyObject().fromJSON({...data});
94
+ * Populates the current instance's properties with data from a JSON string.
95
+ * Automatically parses the JSON string before populating properties.
72
96
  *
73
- * @param {object} json
74
- * @returns {this}
97
+ * @param {string} str - The JSON string to parse and use for populating the instance
98
+ * @param {Partial<SerializationSettings>} [settings] - Optional settings to override default serialization behavior
99
+ * @returns {this} The current instance for method chaining
75
100
  * @memberof Serializable
101
+ * @throws {SyntaxError} If the string is not valid JSON
102
+ * @example
103
+ * ```typescript
104
+ * const user = new User();
105
+ * user.fromString('{"name":"John","age":30}');
106
+ * ```
76
107
  */
77
- fromJSON(json, settings) {
78
- const unknownJson = json;
79
- if (unknownJson === null ||
80
- Array.isArray(unknownJson) ||
81
- typeof unknownJson !== "object") {
82
- this.onWrongType(String(unknownJson), "is not an object", unknownJson);
83
- return this;
84
- }
85
- // eslint-disable-next-line guard-for-in
86
- for (const thisProp in this) {
87
- // Naming strategy and jsonName decorator
88
- let jsonProp = this.getJsonPropertyName(thisProp, settings);
89
- // For deep copy
90
- if (!unknownJson?.hasOwnProperty(jsonProp) && unknownJson?.hasOwnProperty(thisProp)) {
91
- jsonProp = thisProp;
92
- }
93
- if (unknownJson?.hasOwnProperty(jsonProp) &&
94
- this.hasOwnProperty(thisProp) &&
95
- Reflect.hasMetadata("ts-serializable:jsonTypes", this.constructor.prototype, thisProp)) {
96
- const acceptedTypes = Reflect.getMetadata("ts-serializable:jsonTypes", this.constructor.prototype, thisProp);
97
- const jsonValue = Reflect.get(unknownJson, jsonProp);
98
- const extractedValue = this.deserializeProperty(thisProp, acceptedTypes, jsonValue, settings);
99
- Reflect.set(this, thisProp, extractedValue);
100
- }
101
- }
102
- return this;
108
+ fromString(str, settings) {
109
+ return this.fromJSON(JSON.parse(str), settings);
103
110
  }
104
111
  /**
105
- * Process serialization for the @jsonIgnore decorator.
112
+ * Serializes the current instance to a plain JavaScript object.
113
+ * Respects @JsonIgnore decorators to exclude specific properties from serialization.
114
+ * Applies naming strategies and @JsonName decorators for property name transformation.
106
115
  *
107
- * @returns {object}
116
+ * @returns {Record<string, unknown>} A plain object representation of the instance
108
117
  * @memberof Serializable
118
+ * @example
119
+ * ```typescript
120
+ * const user = new User();
121
+ * user.name = "John";
122
+ * user.age = 30;
123
+ * const json = user.toJSON(); // { name: "John", age: 30 }
124
+ * ```
109
125
  */
110
126
  toJSON() {
111
- const toJson = {};
112
- const keys = Reflect.ownKeys(this);
113
- for (const key of keys) {
114
- if (typeof key === "symbol") {
115
- // eslint-disable-next-line no-continue
116
- continue;
117
- }
118
- if (this.hasOwnProperty(key)) {
119
- if (Reflect.getMetadata("ts-serializable:jsonIgnore", this.constructor.prototype, key) !== true) {
120
- const toProp = this.getJsonPropertyName(key);
121
- Reflect.set(toJson, toProp, Reflect.get(this, key));
122
- }
123
- }
124
- }
125
- return toJson;
127
+ return toJSON(this);
126
128
  }
127
129
  /**
128
- * Serialize the class to FormData.
129
- *
130
- * Can be used to prepare an AJAX form with files.
131
- * Sending files via AJAX JSON is a heavy task because it requires converting files to base64 format.
132
- * The user interface can freeze for several seconds during this operation if the file is too large.
133
- * AJAX forms are a lightweight alternative.
134
- *
135
- * @param {string} formPrefix Prefix for form property names
136
- * @param {FormData} formData Can update an existing FormData
137
- * @returns {FormData}
130
+ * Serializes the current instance to FormData format, suitable for multipart/form-data requests.
131
+ * This is particularly useful for AJAX form submissions that include file uploads.
132
+ * Unlike JSON serialization with base64-encoded files, FormData provides better performance
133
+ * and avoids UI freezing when handling large files.
134
+ *
135
+ * @param {string} [formPrefix] - Optional prefix to prepend to all form field names
136
+ * @param {FormData} [formData] - Optional existing FormData instance to append to
137
+ * @returns {FormData} A FormData instance containing the serialized data
138
138
  * @memberof Serializable
139
+ * @example
140
+ * ```typescript
141
+ * const user = new User();
142
+ * user.name = "John";
143
+ * user.avatar = fileInput.files[0];
144
+ * const formData = user.toFormData();
145
+ * // Use with fetch: fetch('/api/users', { method: 'POST', body: formData });
146
+ * ```
139
147
  */
140
148
  toFormData(formPrefix, formData) {
141
149
  return classToFormData(this, formPrefix, formData);
142
150
  }
143
151
  /**
144
- * Process serialization for the @jsonIgnore decorator.
152
+ * Serializes the current instance to a JSON string.
153
+ * This is a convenience method that combines toJSON() with JSON.stringify().
145
154
  *
146
- * @returns {string}
155
+ * @returns {string} A JSON string representation of the instance
147
156
  * @memberof Serializable
157
+ * @example
158
+ * ```typescript
159
+ * const user = new User();
160
+ * user.name = "John";
161
+ * user.age = 30;
162
+ * const jsonString = user.toString(); // '{"name":"John","age":30}'
163
+ * ```
148
164
  */
149
165
  toString() {
150
166
  return JSON.stringify(this.toJSON());
151
167
  }
152
168
  /**
153
- * Process exceptions for incorrect types.
154
- * By default, it just prints a warning in the console, but it can be overridden to throw exceptions or log to the backend.
155
- *
156
- * @protected
157
- * @param {string} prop
158
- * @param {string} message
159
- * @param {(unknown)} jsonValue
169
+ * Handles type mismatch errors during deserialization.
170
+ * By default, logs an error to the console. Can be overridden in subclasses to implement
171
+ * custom error handling such as throwing exceptions, logging to external services, or
172
+ * collecting validation errors.
173
+ *
174
+ * @public
175
+ * @param {string} prop - The name of the property that has a type mismatch
176
+ * @param {string} message - A description of the type error
177
+ * @param {unknown} jsonValue - The actual value that caused the type mismatch
160
178
  * @memberof Serializable
179
+ * @example
180
+ * ```typescript
181
+ * class User extends Serializable {
182
+ * onWrongType(prop: string, message: string, jsonValue: unknown): void {
183
+ * throw new Error(`Invalid ${prop}: ${message}`);
184
+ * }
185
+ * }
186
+ * ```
161
187
  */
162
188
  onWrongType(prop, message, jsonValue) {
163
189
  // eslint-disable-next-line no-console
164
190
  console.error(`${this.constructor.name}.fromJSON: json.${prop} ${message}:`, jsonValue);
165
191
  }
166
192
  /**
167
- * Deserialize one property.
168
- *
169
- * @private
170
- * @param {object} object
171
- * @param {string} prop
172
- * @param {AcceptedTypes[]} acceptedTypes
173
- * @param {(unknown)} jsonValue
174
- * @returns {(Object | null | void)}
193
+ * Deserializes a single property value based on its accepted types.
194
+ * This method is used internally during deserialization to convert JSON values
195
+ * to the appropriate TypeScript types defined by decorators.
196
+ *
197
+ * @public
198
+ * @param {string} prop - The name of the property being deserialized
199
+ * @param {AcceptedTypes[]} acceptedTypes - Array of allowed types for this property
200
+ * @param {unknown} jsonValue - The JSON value to deserialize
201
+ * @param {Partial<SerializationSettings>} [settings] - Optional settings to override default behavior
202
+ * @returns {unknown} The deserialized value matching one of the accepted types
175
203
  * @memberof Serializable
176
204
  */
177
205
  // eslint-disable-next-line max-params
178
206
  deserializeProperty(prop, acceptedTypes, jsonValue, settings) {
179
- for (const acceptedType of acceptedTypes) { // Type Symbol is not a property
180
- if ( // Null
181
- acceptedType === null &&
182
- jsonValue === null) {
183
- return null;
184
- }
185
- else if ( // Void, for deep copy classes only, JSON doesn't have a void type
186
- acceptedType === void 0 &&
187
- jsonValue === void 0) {
188
- return void 0;
189
- }
190
- else if ( // Boolean, Boolean
191
- acceptedType === Boolean &&
192
- (typeof jsonValue === "boolean" || jsonValue instanceof Boolean)) {
193
- return Boolean(jsonValue);
194
- }
195
- else if ( // Number, Number
196
- acceptedType === Number &&
197
- (typeof jsonValue === "number" || jsonValue instanceof Number)) {
198
- return Number(jsonValue);
199
- }
200
- else if ( // String, String
201
- acceptedType === String &&
202
- (typeof jsonValue === "string" || jsonValue instanceof String)) {
203
- return String(jsonValue);
204
- }
205
- else if ( // Object, Object
206
- acceptedType === Object &&
207
- (typeof jsonValue === "object")) {
208
- return Object(jsonValue);
209
- }
210
- else if ( // Date
211
- acceptedType === Date &&
212
- (typeof jsonValue === "string" || jsonValue instanceof String || jsonValue instanceof Date)) {
213
- // 0 year, 0 month, 0 days, 0 hours, 0 minutes, 0 seconds
214
- let unicodeTime = new Date("0000-01-01T00:00:00.000").getTime();
215
- if (typeof jsonValue === "string") {
216
- unicodeTime = Date.parse(jsonValue);
217
- }
218
- else if (jsonValue instanceof String) {
219
- unicodeTime = Date.parse(String(jsonValue));
220
- }
221
- else if (jsonValue instanceof Date) {
222
- unicodeTime = jsonValue.getTime();
223
- }
224
- if (isNaN(unicodeTime)) { // Preserve invalid time
225
- this.onWrongType(prop, "is an invalid date", jsonValue);
226
- }
227
- return new Date(unicodeTime);
228
- }
229
- else if ( // Array
230
- Array.isArray(acceptedType) &&
231
- Array.isArray(jsonValue)) {
232
- if (acceptedType[0] === void 0) {
233
- this.onWrongType(prop, "invalid type", jsonValue);
234
- }
235
- return jsonValue.map((arrayValue) => this.deserializeProperty(prop, acceptedType, arrayValue, settings));
236
- }
237
- else if ( // Serializable
238
- acceptedType !== null &&
239
- acceptedType !== void 0 &&
240
- !Array.isArray(acceptedType) &&
241
- (acceptedType.prototype instanceof Serializable ||
242
- Boolean(Reflect.getMetadata("ts-serializable:jsonObjectExtended", acceptedType))) &&
243
- jsonValue !== null &&
244
- jsonValue !== void 0 &&
245
- typeof jsonValue === "object" && !Array.isArray(jsonValue)) {
246
- const TypeConstructor = acceptedType;
247
- return new TypeConstructor().fromJSON(jsonValue, settings);
248
- }
249
- else if ( // Instance any other class, not Serializable, for parsing from other class instances
250
- acceptedType instanceof Function &&
251
- jsonValue instanceof acceptedType) {
252
- return jsonValue;
253
- }
254
- }
255
- // Process incorrect type and return default value
256
- this.onWrongType(prop, "is invalid", jsonValue);
257
- return Reflect.get(this, prop);
207
+ return deserializeProperty(this, prop, acceptedTypes, jsonValue, settings);
258
208
  }
259
209
  /**
260
- * Extract the correct name for a property.
261
- * Considers decorators for transforming the property name.
262
- *
263
- * @param {string} property Source name of the property
264
- * @param {Partial<SerializationSettings>} settings Serialization settings
265
- * @returns
210
+ * Determines the JSON property name for a given class property.
211
+ * Takes into account @JsonName decorators and naming strategies (camelCase, snake_case, etc.)
212
+ * defined in the serialization settings.
213
+ *
214
+ * @public
215
+ * @param {string} property - The source property name as defined in the class
216
+ * @param {Partial<SerializationSettings>} [settings] - Optional settings to override default naming behavior
217
+ * @returns {string} The transformed property name to use in JSON
218
+ * @memberof Serializable
219
+ * @example
220
+ * ```typescript
221
+ * // With @JsonName decorator
222
+ * class User extends Serializable {
223
+ * @JsonName("user_name")
224
+ * userName: string;
225
+ * }
226
+ * // user.getJsonPropertyName("userName") returns "user_name"
227
+ * ```
266
228
  */
267
229
  getJsonPropertyName(property, settings) {
268
230
  return getPropertyName(this, property, settings);
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Converts a class instance to FormData format for multipart/form-data HTTP requests.
3
+ * This function recursively processes nested objects, arrays, and handles special types like File and Date.
4
+ * Properties marked with @JsonIgnore decorator are excluded from the conversion.
5
+ *
6
+ * @param {object} obj - The class instance or object to convert to FormData
7
+ * @param {string} [formPrefix] - Optional prefix for form property names (used for nested objects, e.g., "user.address")
8
+ * @param {FormData} [formData] - Optional existing FormData instance to append to. If not provided, creates a new one
9
+ * @returns {FormData} The FormData object containing all serialized properties
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const user = {
14
+ * name: "John",
15
+ * age: 30,
16
+ * avatar: fileInput.files[0],
17
+ * address: { city: "New York" }
18
+ * };
19
+ * const formData = classToFormData(user);
20
+ * // Results in FormData with entries:
21
+ * // name: "John"
22
+ * // age: "30"
23
+ * // avatar: [File object]
24
+ * // address.city: "New York"
25
+ * ```
26
+ *
27
+ * @remarks
28
+ * - File objects are appended directly to FormData
29
+ * - Date objects are converted to ISO strings
30
+ * - Null values are skipped
31
+ * - Arrays are processed recursively with indices for nested objects
32
+ * - Nested objects use dot notation for property names
33
+ */
34
+ export declare const classToFormData: (obj: object, formPrefix?: string, formData?: FormData) => FormData;
@@ -1,13 +1,38 @@
1
1
  /* eslint-disable max-statements */
2
2
  /* eslint-disable max-lines-per-function */
3
- import { getPropertyName } from "./GetProperyName.js";
3
+ import { getPropertyName } from "./GetPropertyName.js";
4
4
  /**
5
- * Converts a class instance to FormData for use in AJAX forms.
5
+ * Converts a class instance to FormData format for multipart/form-data HTTP requests.
6
+ * This function recursively processes nested objects, arrays, and handles special types like File and Date.
7
+ * Properties marked with @JsonIgnore decorator are excluded from the conversion.
6
8
  *
7
- * @param {object} obj - The class instance to convert.
8
- * @param {string} [formPrefix] - Optional prefix for form property names.
9
- * @param {FormData} [formData] - Optional existing FormData to update.
10
- * @returns {FormData} - The resulting FormData object.
9
+ * @param {object} obj - The class instance or object to convert to FormData
10
+ * @param {string} [formPrefix] - Optional prefix for form property names (used for nested objects, e.g., "user.address")
11
+ * @param {FormData} [formData] - Optional existing FormData instance to append to. If not provided, creates a new one
12
+ * @returns {FormData} The FormData object containing all serialized properties
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const user = {
17
+ * name: "John",
18
+ * age: 30,
19
+ * avatar: fileInput.files[0],
20
+ * address: { city: "New York" }
21
+ * };
22
+ * const formData = classToFormData(user);
23
+ * // Results in FormData with entries:
24
+ * // name: "John"
25
+ * // age: "30"
26
+ * // avatar: [File object]
27
+ * // address.city: "New York"
28
+ * ```
29
+ *
30
+ * @remarks
31
+ * - File objects are appended directly to FormData
32
+ * - Date objects are converted to ISO strings
33
+ * - Null values are skipped
34
+ * - Arrays are processed recursively with indices for nested objects
35
+ * - Nested objects use dot notation for property names
11
36
  */
12
37
  export const classToFormData = (obj, formPrefix, formData) => {
13
38
  const newFormData = formData ?? new FormData();
@@ -0,0 +1,38 @@
1
+ import { AcceptedTypes } from "../models/AcceptedType.js";
2
+ import { SerializationSettings } from "../models/SerializationSettings.js";
3
+ /**
4
+ * Deserializes a single property value from JSON data based on accepted type definitions.
5
+ * This function iterates through accepted types and attempts to match and convert the JSON value
6
+ * to the appropriate TypeScript type. Supports primitives, arrays, dates, and complex object types.
7
+ *
8
+ * @param {object} obj - The object instance to which the property belongs
9
+ * @param {string} prop - The name of the property being deserialized
10
+ * @param {AcceptedTypes[]} acceptedTypes - Array of type constructors or type definitions that the property can accept
11
+ * @param {unknown} jsonValue - The raw JSON value to deserialize and convert
12
+ * @param {Partial<SerializationSettings>} [settings] - Optional settings to customize deserialization behavior
13
+ * @returns {unknown} The deserialized and typed value, or the original property value if no type match is found
14
+ *
15
+ * @remarks
16
+ * Supported type conversions:
17
+ * - `null` - Preserves null values
18
+ * - `undefined` (void 0) - For deep copy operations
19
+ * - `Boolean` - Converts boolean values and Boolean objects
20
+ * - `Number` - Converts numeric values and Number objects
21
+ * - `String` - Converts string values and String objects
22
+ * - `Object` - Converts plain objects
23
+ * - `Date` - Parses ISO strings, Date objects, and validates date values
24
+ * - `Array` - Recursively deserializes array elements
25
+ * - `Serializable` subclasses - Creates instances and calls fromJSON
26
+ * - Custom classes - Creates instances and applies fromJSON for non-Serializable classes
27
+ * - Instance checks - Validates existing instances of specific classes
28
+ *
29
+ * If no type matches, calls `onWrongType` error handler and returns the original property value.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const acceptedTypes = [String, Number];
34
+ * const result = deserializeProperty(obj, "age", acceptedTypes, "30");
35
+ * // Returns the string "30" since String is checked first
36
+ * ```
37
+ */
38
+ export declare const deserializeProperty: (obj: object, prop: string, acceptedTypes: AcceptedTypes[], jsonValue: unknown, settings?: Partial<SerializationSettings>) => unknown;