Skip to content

Commit 1cbb4ba

Browse files
committed
refactor: reorganize functions and improve serialization methods in Serializable class
1 parent c4cf3fc commit 1cbb4ba

File tree

7 files changed

+221
-150
lines changed

7 files changed

+221
-150
lines changed

src/classes/Serializable.ts

Lines changed: 18 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
/* eslint-disable max-lines */
21
/* 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 */
72
/* eslint-disable @typescript-eslint/no-unsafe-argument */
83

4+
import {deserializeProperty} from "../functions/DeserializeProperty.js";
95
import type {AcceptedTypes} from "../models/AcceptedType.js";
106
import {SerializationSettings} from "../models/SerializationSettings.js";
11-
import {classToFormData} from "../utils/ClassToFormData.js";
12-
import {getPropertyName} from "../utils/GetProperyName.js";
7+
import {classToFormData} from "../functions/ClassToFormData.js";
8+
import {getPropertyName} from "../functions/GetProperyName.js";
9+
import {fromJSON} from "../functions/FromJSON.js";
1310

1411
/**
1512
* Class that helps you deserialize objects to classes.
@@ -67,68 +64,31 @@ export class Serializable {
6764
}
6865

6966
/**
70-
* Fill properties of the current model with data from a string.
67+
* Fill properties of the current model with data from JSON.
7168
*
7269
* Example:
73-
* const obj: MyObject = new MyObject().fromString("{...data}");
70+
* const obj: MyObject = new MyObject().fromJSON({...data});
7471
*
75-
* @param {string} str
72+
* @param {object} json
7673
* @returns {this}
7774
* @memberof Serializable
7875
*/
79-
public fromString (str: string, settings?: Partial<SerializationSettings>): this {
80-
return this.fromJSON(JSON.parse(str), settings);
76+
public fromJSON (json: object, settings?: Partial<SerializationSettings>): this {
77+
return fromJSON<this>(this, json, settings);
8178
}
8279

8380
/**
84-
* Fill properties of the current model with data from JSON.
81+
* Fill properties of the current model with data from a string.
8582
*
8683
* Example:
87-
* const obj: MyObject = new MyObject().fromJSON({...data});
84+
* const obj: MyObject = new MyObject().fromString("{...data}");
8885
*
89-
* @param {object} json
86+
* @param {string} str
9087
* @returns {this}
9188
* @memberof Serializable
9289
*/
93-
public fromJSON (json: object, settings?: Partial<SerializationSettings>): this {
94-
const unknownJson: unknown = json;
95-
96-
if (
97-
unknownJson === null ||
98-
Array.isArray(unknownJson) ||
99-
typeof unknownJson !== "object"
100-
) {
101-
this.onWrongType(String(unknownJson), "is not an object", unknownJson);
102-
return this;
103-
}
104-
105-
// eslint-disable-next-line guard-for-in
106-
for (const thisProp in this) {
107-
// Naming strategy and jsonName decorator
108-
let jsonProp: string = this.getJsonPropertyName(thisProp, settings);
109-
110-
// For deep copy
111-
if (!unknownJson?.hasOwnProperty(jsonProp) && unknownJson?.hasOwnProperty(thisProp)) {
112-
jsonProp = thisProp;
113-
}
114-
115-
if (
116-
unknownJson?.hasOwnProperty(jsonProp) &&
117-
this.hasOwnProperty(thisProp) &&
118-
Reflect.hasMetadata("ts-serializable:jsonTypes", this.constructor.prototype, thisProp)
119-
) {
120-
const acceptedTypes: AcceptedTypes[] = Reflect.getMetadata(
121-
"ts-serializable:jsonTypes",
122-
this.constructor.prototype,
123-
thisProp
124-
) as [];
125-
const jsonValue: unknown = Reflect.get(unknownJson, jsonProp) as unknown;
126-
const extractedValue = this.deserializeProperty(thisProp, acceptedTypes, jsonValue, settings);
127-
Reflect.set(this, thisProp, extractedValue);
128-
}
129-
}
130-
131-
return this;
90+
public fromString (str: string, settings?: Partial<SerializationSettings>): this {
91+
return this.fromJSON(JSON.parse(str), settings);
13292
}
13393

13494
/**
@@ -195,7 +155,7 @@ export class Serializable {
195155
* @param {(unknown)} jsonValue
196156
* @memberof Serializable
197157
*/
198-
protected onWrongType (prop: string, message: string, jsonValue: unknown): void {
158+
public onWrongType (prop: string, message: string, jsonValue: unknown): void {
199159
// eslint-disable-next-line no-console
200160
console.error(`${this.constructor.name}.fromJSON: json.${prop} ${message}:`, jsonValue);
201161
}
@@ -212,103 +172,13 @@ export class Serializable {
212172
* @memberof Serializable
213173
*/
214174
// eslint-disable-next-line max-params
215-
protected deserializeProperty (
175+
public deserializeProperty (
216176
prop: string,
217177
acceptedTypes: AcceptedTypes[],
218178
jsonValue: unknown,
219179
settings?: Partial<SerializationSettings>
220180
): unknown {
221-
for (const acceptedType of acceptedTypes) { // Type Symbol is not a property
222-
if (// Null
223-
acceptedType === null &&
224-
jsonValue === null
225-
) {
226-
return null;
227-
} else if (// Void, for deep copy classes only, JSON doesn't have a void type
228-
acceptedType === void 0 &&
229-
jsonValue === void 0
230-
) {
231-
return void 0;
232-
} else if (// Boolean, Boolean
233-
acceptedType === Boolean &&
234-
(typeof jsonValue === "boolean" || jsonValue instanceof Boolean)
235-
) {
236-
return Boolean(jsonValue);
237-
} else if (// Number, Number
238-
acceptedType === Number &&
239-
(typeof jsonValue === "number" || jsonValue instanceof Number)
240-
) {
241-
return Number(jsonValue);
242-
} else if (// String, String
243-
acceptedType === String &&
244-
(typeof jsonValue === "string" || jsonValue instanceof String)
245-
) {
246-
return String(jsonValue);
247-
} else if (// Object, Object
248-
acceptedType === Object &&
249-
(typeof jsonValue === "object")
250-
) {
251-
return Object(jsonValue);
252-
} else if (// Date
253-
acceptedType === Date &&
254-
(typeof jsonValue === "string" || jsonValue instanceof String || jsonValue instanceof Date)
255-
) {
256-
// 0 year, 0 month, 0 days, 0 hours, 0 minutes, 0 seconds
257-
let unicodeTime: number = new Date("0000-01-01T00:00:00.000").getTime();
258-
259-
if (typeof jsonValue === "string") {
260-
unicodeTime = Date.parse(jsonValue);
261-
} else if (jsonValue instanceof String) {
262-
unicodeTime = Date.parse(String(jsonValue));
263-
} else if (jsonValue instanceof Date) {
264-
unicodeTime = jsonValue.getTime();
265-
}
266-
if (isNaN(unicodeTime)) { // Preserve invalid time
267-
this.onWrongType(prop, "is an invalid date", jsonValue);
268-
}
269-
270-
return new Date(unicodeTime);
271-
} else if (// Array
272-
Array.isArray(acceptedType) &&
273-
Array.isArray(jsonValue)
274-
) {
275-
if (acceptedType[0] === void 0) {
276-
this.onWrongType(prop, "invalid type", jsonValue);
277-
}
278-
279-
return jsonValue.map((arrayValue: unknown) => this.deserializeProperty(
280-
prop,
281-
acceptedType,
282-
arrayValue,
283-
settings
284-
));
285-
} else if (// Serializable
286-
acceptedType !== null &&
287-
acceptedType !== void 0 &&
288-
!Array.isArray(acceptedType) &&
289-
(
290-
acceptedType.prototype instanceof Serializable ||
291-
Boolean(Reflect.getMetadata("ts-serializable:jsonObjectExtended", acceptedType))
292-
) &&
293-
jsonValue !== null &&
294-
jsonValue !== void 0 &&
295-
typeof jsonValue === "object" && !Array.isArray(jsonValue)
296-
) {
297-
const TypeConstructor: new () => Serializable = acceptedType as new () => Serializable;
298-
299-
return new TypeConstructor().fromJSON(jsonValue, settings);
300-
} else if (// Instance any other class, not Serializable, for parsing from other class instances
301-
acceptedType instanceof Function &&
302-
jsonValue instanceof acceptedType
303-
) {
304-
return jsonValue;
305-
}
306-
}
307-
308-
// Process incorrect type and return default value
309-
this.onWrongType(prop, "is invalid", jsonValue);
310-
311-
return Reflect.get(this, prop);
181+
return deserializeProperty(this, prop, acceptedTypes, jsonValue, settings);
312182
}
313183

314184
/**
@@ -319,7 +189,7 @@ export class Serializable {
319189
* @param {Partial<SerializationSettings>} settings Serialization settings
320190
* @returns
321191
*/
322-
protected getJsonPropertyName (property: string, settings?: Partial<SerializationSettings>): string {
192+
public getJsonPropertyName (property: string, settings?: Partial<SerializationSettings>): string {
323193
return getPropertyName(this, property, settings);
324194
}
325195

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import {Serializable} from "../classes/Serializable";
2+
import {AcceptedTypes} from "../models/AcceptedType";
3+
import {SerializationSettings} from "../models/SerializationSettings";
4+
import {onWrongType} from "./OnWrongType";
5+
6+
/**
7+
* Deserializes a property value from JSON based on the accepted types.
8+
* This function attempts to convert the provided JSON value into one of the specified accepted types,
9+
* handling primitives, arrays, dates, and serializable objects.
10+
*
11+
* @param obj - The object instance to which the property belongs.
12+
* @param prop - The name of the property being deserialized.
13+
* @param acceptedTypes - An array of accepted types for the property.
14+
* @param jsonValue - The JSON value to deserialize.
15+
* @param settings - Optional serialization settings to customize the deserialization process.
16+
* @returns The deserialized value matching one of the accepted types, or the original property value if deserialization fails.
17+
*/
18+
// eslint-disable-next-line max-lines-per-function, max-statements, complexity
19+
export const deserializeProperty = (
20+
obj: object,
21+
prop: string,
22+
acceptedTypes: AcceptedTypes[],
23+
jsonValue: unknown,
24+
settings?: Partial<SerializationSettings>
25+
// eslint-disable-next-line max-params
26+
): unknown => {
27+
for (const acceptedType of acceptedTypes) { // Type Symbol is not a property
28+
if (// Null
29+
acceptedType === null &&
30+
jsonValue === null
31+
) {
32+
return null;
33+
} else if (// Void, for deep copy classes only, JSON doesn't have a void type
34+
acceptedType === void 0 &&
35+
jsonValue === void 0
36+
) {
37+
return void 0;
38+
} else if (// Boolean, Boolean
39+
acceptedType === Boolean &&
40+
(typeof jsonValue === "boolean" || jsonValue instanceof Boolean)
41+
) {
42+
return Boolean(jsonValue);
43+
} else if (// Number, Number
44+
acceptedType === Number &&
45+
(typeof jsonValue === "number" || jsonValue instanceof Number)
46+
) {
47+
return Number(jsonValue);
48+
} else if (// String, String
49+
acceptedType === String &&
50+
(typeof jsonValue === "string" || jsonValue instanceof String)
51+
) {
52+
return String(jsonValue);
53+
} else if (// Object, Object
54+
acceptedType === Object &&
55+
(typeof jsonValue === "object")
56+
) {
57+
return Object(jsonValue);
58+
} else if (// Date
59+
acceptedType === Date &&
60+
(typeof jsonValue === "string" || jsonValue instanceof String || jsonValue instanceof Date)
61+
) {
62+
// 0 year, 0 month, 0 days, 0 hours, 0 minutes, 0 seconds
63+
let unicodeTime: number = new Date("0000-01-01T00:00:00.000").getTime();
64+
65+
if (typeof jsonValue === "string") {
66+
unicodeTime = Date.parse(jsonValue);
67+
} else if (jsonValue instanceof String) {
68+
unicodeTime = Date.parse(String(jsonValue));
69+
} else if (jsonValue instanceof Date) {
70+
unicodeTime = jsonValue.getTime();
71+
}
72+
if (isNaN(unicodeTime)) { // Preserve invalid time
73+
if (obj instanceof Serializable) {
74+
obj.onWrongType(prop, "is an invalid date", jsonValue);
75+
} else {
76+
onWrongType(obj, prop, "is an invalid date", jsonValue);
77+
}
78+
}
79+
80+
return new Date(unicodeTime);
81+
} else if (// Array
82+
Array.isArray(acceptedType) &&
83+
Array.isArray(jsonValue)
84+
) {
85+
if (acceptedType[0] === void 0) {
86+
if (obj instanceof Serializable) {
87+
obj.onWrongType(prop, "invalid type", jsonValue);
88+
} else {
89+
onWrongType(obj, prop, "invalid type", jsonValue);
90+
}
91+
}
92+
93+
return jsonValue.map((arrayValue: unknown) => deserializeProperty(
94+
obj,
95+
prop,
96+
acceptedType,
97+
arrayValue,
98+
settings
99+
));
100+
} else if (// Serializable
101+
acceptedType !== null &&
102+
acceptedType !== void 0 &&
103+
!Array.isArray(acceptedType) &&
104+
(
105+
acceptedType.prototype instanceof Serializable ||
106+
Boolean(Reflect.getMetadata("ts-serializable:jsonObjectExtended", acceptedType))
107+
) &&
108+
jsonValue !== null &&
109+
jsonValue !== void 0 &&
110+
typeof jsonValue === "object" && !Array.isArray(jsonValue)
111+
) {
112+
const TypeConstructor: new () => Serializable = acceptedType as new () => Serializable;
113+
114+
return new TypeConstructor().fromJSON(jsonValue, settings);
115+
} else if (// Instance any other class, not Serializable, for parsing from other class instances
116+
acceptedType instanceof Function &&
117+
jsonValue instanceof acceptedType
118+
) {
119+
return jsonValue;
120+
}
121+
}
122+
123+
// Process incorrect type and return default value
124+
if (obj instanceof Serializable) {
125+
obj.onWrongType(prop, "is invalid", jsonValue);
126+
} else {
127+
onWrongType(obj, prop, "is invalid", jsonValue);
128+
}
129+
130+
return Reflect.get(obj, prop);
131+
};

0 commit comments

Comments
 (0)