ts-serializable 3.6.0 → 3.7.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 CHANGED
@@ -181,6 +181,33 @@ JSON.stringify(user);
181
181
  // Result: {"firstName":"","familyName":""}
182
182
  ```
183
183
 
184
+ Class to FormData
185
+ ------
186
+
187
+ Sometimes classes contain properties with the File type. Sending such classes via json is a heavy task. Converting a file property to json can freeze the interface for a few seconds if the file is large. A much better solution is to send an Ajax form. Example:
188
+
189
+ ```typescript
190
+ import { Serializable } from "ts-serializable";
191
+
192
+ export class User extends Serializable {
193
+
194
+ public firstName: string = '';
195
+
196
+ public familyName: File | null = null;
197
+
198
+ }
199
+
200
+ // ... send file function ...
201
+
202
+ await fetch("api/sendFile", {
203
+ method: "POST",
204
+ body: user.toFormData() // <- serialization class to FormData
205
+ });
206
+
207
+ ```
208
+
209
+ Naming strategies, custom names, ignoring and other decorators are supported during conversion.
210
+
184
211
  Bonus
185
212
  ------
186
213
 
@@ -68,6 +68,20 @@ export declare class Serializable {
68
68
  * @memberof Serializable
69
69
  */
70
70
  toJSON(): Record<string, unknown>;
71
+ /**
72
+ * Serialize class to FormData.
73
+ *
74
+ * Can be used for prepare ajax form with files.
75
+ * Send files via ajax json its heavy task, because need convert file to base 64 format,
76
+ * user interface can be freeze on many seconds on this operation if file is too big.
77
+ * Ajax forms its lightweight alternative.
78
+ *
79
+ * @param {string} formPrefix Prefix for form property names
80
+ * @param {FormData} formData Can be update an existing FormData
81
+ * @returns {FormData}
82
+ * @memberof Serializable
83
+ */
84
+ toFormData(formPrefix?: string, formData?: FormData): FormData;
71
85
  /**
72
86
  * Process serialization for @jsonIgnore decorator
73
87
  *
@@ -98,5 +112,13 @@ export declare class Serializable {
98
112
  * @memberof Serializable
99
113
  */
100
114
  protected deserializeProperty(prop: string, acceptedTypes: AcceptedTypes[], jsonValue: unknown, settings?: Partial<SerializationSettings>): unknown;
101
- protected getJsonPropertyName(thisProperty: string, settings?: Partial<SerializationSettings>): string;
115
+ /**
116
+ * Extract correct name for property.
117
+ * Considers decorators for transforming the property name.
118
+ *
119
+ * @param {string} property Source name of property
120
+ * @param {Partial<SerializationSettings>} settings Serialization settings
121
+ * @returns
122
+ */
123
+ protected getJsonPropertyName(property: string, settings?: Partial<SerializationSettings>): string;
102
124
  }
@@ -6,6 +6,8 @@
6
6
  /* eslint-disable max-statements */
7
7
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
8
8
  import { SerializationSettings } from "../models/SerializationSettings.js";
9
+ import { classToFormData } from "../utils/ClassToFormData.js";
10
+ import { getPropertyName } from "../utils/GetProperyName.js";
9
11
  /**
10
12
  * Class how help you deserialize object to classes.
11
13
  *
@@ -98,19 +100,38 @@ export class Serializable {
98
100
  * @memberof Serializable
99
101
  */
100
102
  toJSON() {
101
- const fromJson = { ...this };
102
103
  const toJson = {};
103
- for (const prop in fromJson) {
104
- // Json.hasOwnProperty(prop) - preserve for deserialization for other classes with methods
105
- if (fromJson.hasOwnProperty(prop) && this.hasOwnProperty(prop)) {
106
- if (Reflect.getMetadata("ts-serializable:jsonIgnore", this.constructor.prototype, prop) !== true) {
107
- const toProp = this.getJsonPropertyName(prop);
108
- Reflect.set(toJson, toProp, Reflect.get(fromJson, prop));
104
+ const keys = Reflect.ownKeys(this);
105
+ for (const key of keys) {
106
+ if (typeof key === "symbol") {
107
+ // eslint-disable-next-line no-continue
108
+ continue;
109
+ }
110
+ if (this.hasOwnProperty(key)) {
111
+ if (Reflect.getMetadata("ts-serializable:jsonIgnore", this.constructor.prototype, key) !== true) {
112
+ const toProp = this.getJsonPropertyName(key);
113
+ Reflect.set(toJson, toProp, Reflect.get(this, key));
109
114
  }
110
115
  }
111
116
  }
112
117
  return toJson;
113
118
  }
119
+ /**
120
+ * Serialize class to FormData.
121
+ *
122
+ * Can be used for prepare ajax form with files.
123
+ * Send files via ajax json its heavy task, because need convert file to base 64 format,
124
+ * user interface can be freeze on many seconds on this operation if file is too big.
125
+ * Ajax forms its lightweight alternative.
126
+ *
127
+ * @param {string} formPrefix Prefix for form property names
128
+ * @param {FormData} formData Can be update an existing FormData
129
+ * @returns {FormData}
130
+ * @memberof Serializable
131
+ */
132
+ toFormData(formPrefix, formData) {
133
+ return classToFormData(this, formPrefix, formData);
134
+ }
114
135
  /**
115
136
  * Process serialization for @jsonIgnore decorator
116
137
  *
@@ -227,22 +248,16 @@ export class Serializable {
227
248
  this.onWrongType(prop, "is invalid", jsonValue);
228
249
  return Reflect.get(this, prop);
229
250
  }
230
- getJsonPropertyName(thisProperty, settings) {
231
- if (Reflect.hasMetadata("ts-serializable:jsonName", this.constructor.prototype, thisProperty)) {
232
- return Reflect.getMetadata("ts-serializable:jsonName", this.constructor.prototype, thisProperty);
233
- }
234
- if (settings?.namingStrategy) {
235
- return settings.namingStrategy.toJsonName(thisProperty);
236
- }
237
- if (Reflect.hasMetadata("ts-serializable:jsonObject", this.constructor)) {
238
- const objectSettings = Reflect.getMetadata("ts-serializable:jsonObject", this.constructor);
239
- return objectSettings.namingStrategy?.toJsonName(thisProperty) ?? thisProperty;
240
- }
241
- if (Serializable.defaultSettings.namingStrategy) {
242
- const { namingStrategy } = Serializable.defaultSettings;
243
- return namingStrategy.toJsonName(thisProperty) ?? thisProperty;
244
- }
245
- return thisProperty;
251
+ /**
252
+ * Extract correct name for property.
253
+ * Considers decorators for transforming the property name.
254
+ *
255
+ * @param {string} property Source name of property
256
+ * @param {Partial<SerializationSettings>} settings Serialization settings
257
+ * @returns
258
+ */
259
+ getJsonPropertyName(property, settings) {
260
+ return getPropertyName(this, property, settings);
246
261
  }
247
262
  }
248
263
  /**
package/dist/index.d.ts CHANGED
@@ -15,3 +15,5 @@ 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 "./utils/ClassToFormData.js";
19
+ export { getPropertyName } from "./utils/GetProperyName.js";
package/dist/index.js CHANGED
@@ -18,3 +18,6 @@ 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
+ // Utils
22
+ export { classToFormData } from "./utils/ClassToFormData.js";
23
+ export { getPropertyName } from "./utils/GetProperyName.js";
@@ -0,0 +1 @@
1
+ export declare const classToFormData: (obj: object, formPrefix?: string, formData?: FormData) => FormData;
@@ -0,0 +1,56 @@
1
+ import { getPropertyName } from "./GetProperyName.js";
2
+ // eslint-disable-next-line max-statements, max-lines-per-function
3
+ export const classToFormData = (obj, formPrefix, formData) => {
4
+ const newFormData = formData ?? new FormData();
5
+ const keys = Reflect.ownKeys(obj);
6
+ for (const key of keys) {
7
+ if (typeof key === "symbol") {
8
+ // eslint-disable-next-line no-continue
9
+ continue;
10
+ }
11
+ // eslint-disable-next-line no-prototype-builtins
12
+ if (obj.hasOwnProperty(key)) {
13
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
14
+ if (Reflect.getMetadata("ts-serializable:jsonIgnore", obj.constructor.prototype, key) !== true) {
15
+ const name = formPrefix ?
16
+ `${formPrefix}.${getPropertyName(obj, key)}` :
17
+ getPropertyName(obj, key);
18
+ /*
19
+ * The function is defined inside the function to capture variables and
20
+ * solve the problem of order of definition.
21
+ */
22
+ const processValue = (value, index) => {
23
+ if (Array.isArray(value)) {
24
+ for (const [oneIndex, oneVal] of value.entries()) {
25
+ processValue(oneVal, oneIndex);
26
+ }
27
+ }
28
+ else if (value === null) {
29
+ // Null is not sent in the form.
30
+ }
31
+ else if (value instanceof File) {
32
+ newFormData.append(name, value);
33
+ }
34
+ else if (value instanceof Date) {
35
+ newFormData.append(name, value.toISOString());
36
+ }
37
+ else if (typeof value === "object") {
38
+ let prefix = name;
39
+ // For arrays of objects in form need add index
40
+ if (typeof index === "number") {
41
+ prefix += `[${index.toString()}]`;
42
+ }
43
+ classToFormData(value, prefix, formData);
44
+ }
45
+ else {
46
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
47
+ newFormData.append(name, String(value));
48
+ }
49
+ };
50
+ const uValue = Reflect.get(obj, key);
51
+ processValue(uValue);
52
+ }
53
+ }
54
+ }
55
+ return newFormData;
56
+ };
@@ -0,0 +1,2 @@
1
+ import { SerializationSettings } from "../models/SerializationSettings.js";
2
+ export declare const getPropertyName: (obj: object, property: string, settings?: Partial<SerializationSettings>) => string;
@@ -0,0 +1,20 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
2
+ import { Serializable } from "../classes/Serializable.js";
3
+ // eslint-disable-next-line max-statements
4
+ export const getPropertyName = (obj, property, settings) => {
5
+ if (Reflect.hasMetadata("ts-serializable:jsonName", obj.constructor.prototype, property)) {
6
+ return Reflect.getMetadata("ts-serializable:jsonName", obj.constructor.prototype, property);
7
+ }
8
+ if (settings?.namingStrategy) {
9
+ return settings.namingStrategy.toJsonName(property);
10
+ }
11
+ if (Reflect.hasMetadata("ts-serializable:jsonObject", obj.constructor)) {
12
+ const objectSettings = Reflect.getMetadata("ts-serializable:jsonObject", obj.constructor);
13
+ return objectSettings.namingStrategy?.toJsonName(property) ?? property;
14
+ }
15
+ if (Serializable.defaultSettings.namingStrategy) {
16
+ const { namingStrategy } = Serializable.defaultSettings;
17
+ return namingStrategy.toJsonName(property);
18
+ }
19
+ return property;
20
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-serializable",
3
- "version": "3.6.0",
3
+ "version": "3.7.2",
4
4
  "author": "Eugene Labutin",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/LabEG/Serializable#readme",
@@ -23,9 +23,10 @@
23
23
  "scripts": {
24
24
  "lint": "eslint --fix ./src/ ./tests/",
25
25
  "test": "node --import ./ts-loader.js --test --test-reporter=spec --test-reporter-destination=stdout \"tests/**/*.spec.ts\"",
26
+ "test-watch": "node --watch --import ./ts-loader.js --test --test-reporter=spec --test-reporter-destination=stdout \"tests/**/*.spec.ts\"",
26
27
  "coverage": "node --import ./ts-loader.js --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info \"tests/**/*.spec.ts\"",
27
28
  "build": "tsc --project tsconfig.build.json && node ./dist/index.js",
28
- "prepublishOnly": "npm run lint && npm run build && npm run test",
29
+ "prepublishOnly": "npm run lint && npm run build && npm run test && node ./dist/index.js",
29
30
  "release": "cliff-jumper --name 'ts-serializable' --package-path '.' --no-skip-changelog --no-skip-tag",
30
31
  "prepare": "husky install"
31
32
  },