@tsed/json-mapper 8.0.1 → 8.0.3
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/package.json +8 -7
- package/readme.md +1 -1
- package/src/components/DateMapper.spec.ts +43 -0
- package/src/components/DateMapper.ts +29 -0
- package/src/components/PrimitiveMapper.spec.ts +179 -0
- package/src/components/PrimitiveMapper.ts +64 -0
- package/src/components/SymbolMapper.spec.ts +23 -0
- package/src/components/SymbolMapper.ts +19 -0
- package/src/decorators/afterDeserialize.ts +39 -0
- package/src/decorators/beforeDeserialize.ts +39 -0
- package/src/decorators/jsonMapper.ts +16 -0
- package/src/decorators/onDeserialize.ts +29 -0
- package/src/decorators/onSerialize.ts +29 -0
- package/src/domain/JsonDeserializer.spec.ts +1437 -0
- package/src/domain/JsonDeserializer.ts +402 -0
- package/src/domain/JsonDeserializerOptions.ts +31 -0
- package/src/domain/JsonMapperCompiler.ts +247 -0
- package/src/domain/JsonMapperGlobalOptions.ts +14 -0
- package/src/domain/JsonMapperSettings.ts +7 -0
- package/src/domain/JsonMapperTypesContainer.ts +20 -0
- package/src/domain/JsonSerializer.spec.ts +1505 -0
- package/src/domain/JsonSerializer.ts +371 -0
- package/src/domain/JsonSerializerOptions.ts +17 -0
- package/src/domain/Writer.spec.ts +11 -0
- package/src/domain/Writer.ts +181 -0
- package/src/domain/__snapshots__/Writer.spec.ts.snap +10 -0
- package/src/hooks/alterAfterDeserialize.ts +5 -0
- package/src/hooks/alterBeforeDeserialize.ts +5 -0
- package/src/hooks/alterOnDeserialize.ts +5 -0
- package/src/hooks/alterOnSerialize.ts +5 -0
- package/src/index.ts +28 -0
- package/src/interfaces/JsonMapperMethods.ts +32 -0
- package/src/utils/deserialize.spec.ts +33 -0
- package/src/utils/deserialize.ts +12 -0
- package/src/utils/getObjectProperties.ts +5 -0
- package/src/utils/serialize.spec.ts +37 -0
- package/src/utils/serialize.ts +12 -0
|
@@ -0,0 +1,1505 @@
|
|
|
1
|
+
import "../components/DateMapper.js";
|
|
2
|
+
import "../components/PrimitiveMapper.js";
|
|
3
|
+
import "../components/SymbolMapper.js";
|
|
4
|
+
|
|
5
|
+
import {cleanObject, isBoolean, isNumber, isObjectID, useDecorators} from "@tsed/core";
|
|
6
|
+
import {
|
|
7
|
+
AdditionalProperties,
|
|
8
|
+
Allow,
|
|
9
|
+
CollectionOf,
|
|
10
|
+
Default,
|
|
11
|
+
DiscriminatorKey,
|
|
12
|
+
DiscriminatorValue,
|
|
13
|
+
Email,
|
|
14
|
+
Groups,
|
|
15
|
+
Ignore,
|
|
16
|
+
JsonHookContext,
|
|
17
|
+
MinLength,
|
|
18
|
+
Name,
|
|
19
|
+
Nullable,
|
|
20
|
+
Property,
|
|
21
|
+
Required,
|
|
22
|
+
Uri
|
|
23
|
+
} from "@tsed/schema";
|
|
24
|
+
import {snakeCase} from "change-case";
|
|
25
|
+
import {parse} from "querystring";
|
|
26
|
+
|
|
27
|
+
import {Post} from "../../test/helpers/Post.js";
|
|
28
|
+
import {User} from "../../test/helpers/User.js";
|
|
29
|
+
import {OnDeserialize} from "../decorators/onDeserialize.js";
|
|
30
|
+
import {OnSerialize} from "../decorators/onSerialize.js";
|
|
31
|
+
import {deserialize} from "../utils/deserialize.js";
|
|
32
|
+
import {JsonMapperSettings} from "./JsonMapperSettings.js";
|
|
33
|
+
import {getJsonMapperTypes} from "./JsonMapperTypesContainer.js";
|
|
34
|
+
import {JsonSerializer} from "./JsonSerializer.js";
|
|
35
|
+
|
|
36
|
+
const serializer = new JsonSerializer();
|
|
37
|
+
|
|
38
|
+
const serialize = (...args: any[]) => (serializer.map as any)(...args);
|
|
39
|
+
|
|
40
|
+
function createMap(value: any) {
|
|
41
|
+
return new Map([["test", value]]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class ObjectId {
|
|
45
|
+
_bsontype = true;
|
|
46
|
+
|
|
47
|
+
constructor(public id: string) {}
|
|
48
|
+
|
|
49
|
+
toString() {
|
|
50
|
+
return this.id;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe("JsonSerializer", () => {
|
|
55
|
+
describe("Primitives", () => {
|
|
56
|
+
it("should serialize values", () => {
|
|
57
|
+
expect(serialize(undefined)).toBeUndefined();
|
|
58
|
+
expect(serialize(null)).toEqual(null);
|
|
59
|
+
expect(serialize("null")).toEqual("null");
|
|
60
|
+
expect(serialize(Symbol.for("TEST"))).toEqual("TEST");
|
|
61
|
+
expect(serialize(false)).toEqual(false);
|
|
62
|
+
expect(serialize(true)).toEqual(true);
|
|
63
|
+
expect(serialize("")).toEqual("");
|
|
64
|
+
expect(serialize("1")).toEqual("1");
|
|
65
|
+
expect(serialize(0)).toEqual(0);
|
|
66
|
+
expect(serialize(1)).toEqual(1);
|
|
67
|
+
expect(serialize(1)).toEqual(1);
|
|
68
|
+
expect(serialize(BigInt(1n))).toEqual(BigInt(1));
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe("Object", () => {
|
|
72
|
+
it("should serialize values", () => {
|
|
73
|
+
expect(serialize({test: "test"}, {type: Object})).toEqual({test: "test"});
|
|
74
|
+
expect(serialize({test: "test"}, {type: false})).toEqual({test: "test"});
|
|
75
|
+
expect(serialize({test: "test"}, {type: null})).toEqual({test: "test"});
|
|
76
|
+
expect(serialize({test: "test"}, {type: undefined})).toEqual({test: "test"});
|
|
77
|
+
});
|
|
78
|
+
it("should serialize parsed querystring", () => {
|
|
79
|
+
expect(serialize({qs: parse("q[offset]=0&q[limit]=10&q[where][a]=0&q[where][b]=1")})).toEqual({
|
|
80
|
+
qs: {
|
|
81
|
+
"q[limit]": "10",
|
|
82
|
+
"q[offset]": "0",
|
|
83
|
+
"q[where][a]": "0",
|
|
84
|
+
"q[where][b]": "1"
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe("Array<primitive>", () => {
|
|
90
|
+
it("should serialize values", () => {
|
|
91
|
+
expect(serialize([null])).toEqual([null]);
|
|
92
|
+
expect(serialize([false])).toEqual([false]);
|
|
93
|
+
expect(serialize([true])).toEqual([true]);
|
|
94
|
+
expect(serialize([""])).toEqual([""]);
|
|
95
|
+
expect(serialize(["1"])).toEqual(["1"]);
|
|
96
|
+
expect(serialize([1])).toEqual([1]);
|
|
97
|
+
|
|
98
|
+
class ArrayLike extends Array {}
|
|
99
|
+
|
|
100
|
+
const arrayLike = new ArrayLike();
|
|
101
|
+
arrayLike.push(1);
|
|
102
|
+
|
|
103
|
+
expect(serialize(arrayLike)).toEqual([1]);
|
|
104
|
+
|
|
105
|
+
class SetLike extends Set {}
|
|
106
|
+
|
|
107
|
+
const setLike = new SetLike();
|
|
108
|
+
setLike.add(1);
|
|
109
|
+
|
|
110
|
+
expect(serialize(setLike)).toEqual([1]);
|
|
111
|
+
|
|
112
|
+
class MapLike extends Map {}
|
|
113
|
+
|
|
114
|
+
const mapLike = new MapLike();
|
|
115
|
+
mapLike.set("i", 1);
|
|
116
|
+
|
|
117
|
+
expect(serialize(mapLike)).toEqual({i: 1});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe("Map<primitive>", () => {
|
|
121
|
+
it("should serialize values", () => {
|
|
122
|
+
expect(serialize(createMap(null))).toEqual({test: null});
|
|
123
|
+
expect(serialize(createMap(false))).toEqual({test: false});
|
|
124
|
+
expect(serialize(createMap(true))).toEqual({test: true});
|
|
125
|
+
expect(serialize(createMap(""))).toEqual({test: ""});
|
|
126
|
+
expect(serialize(createMap("1"))).toEqual({test: "1"});
|
|
127
|
+
expect(serialize(createMap(1))).toEqual({test: 1});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe("toJson()", () => {
|
|
131
|
+
it("should serialize obj from toJSON", () => {
|
|
132
|
+
const result = serialize({
|
|
133
|
+
toJSON() {
|
|
134
|
+
return "hello";
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(result).toEqual("hello");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should serialize obj from toJSON (with type)", () => {
|
|
142
|
+
class Model {}
|
|
143
|
+
|
|
144
|
+
const result = serialize(
|
|
145
|
+
{
|
|
146
|
+
toJSON() {
|
|
147
|
+
return "hello";
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{type: Model}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(result).toEqual("hello");
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe("Plain Object", () => {
|
|
157
|
+
it("should serialize plain object (1)", () => {
|
|
158
|
+
expect(serialize({prop: "1"})).toEqual({prop: "1"});
|
|
159
|
+
|
|
160
|
+
const result = serialize({
|
|
161
|
+
prop: "1",
|
|
162
|
+
roles: [{label: "Admin"}]
|
|
163
|
+
});
|
|
164
|
+
expect(result).toEqual({
|
|
165
|
+
prop: "1",
|
|
166
|
+
roles: [
|
|
167
|
+
{
|
|
168
|
+
label: "Admin"
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
it("should serialize plain object (2)", () => {
|
|
174
|
+
expect(serialize({prop: "1"})).toEqual({prop: "1"});
|
|
175
|
+
const roles = new Map();
|
|
176
|
+
roles.set("ro", "le");
|
|
177
|
+
|
|
178
|
+
const result = serialize({
|
|
179
|
+
prop: "1",
|
|
180
|
+
roles
|
|
181
|
+
});
|
|
182
|
+
expect(result).toEqual({
|
|
183
|
+
prop: "1",
|
|
184
|
+
roles: {
|
|
185
|
+
ro: "le"
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
describe("Class", () => {
|
|
191
|
+
describe("ignore hook is configured on props", () => {
|
|
192
|
+
it("should serialize model (api = true)", () => {
|
|
193
|
+
class Role {
|
|
194
|
+
@Property()
|
|
195
|
+
label: string;
|
|
196
|
+
|
|
197
|
+
constructor({label}: any = {}) {
|
|
198
|
+
this.label = label;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
class Model {
|
|
203
|
+
@Property()
|
|
204
|
+
id: string;
|
|
205
|
+
|
|
206
|
+
@Ignore((ignored, ctx: JsonHookContext) => ctx.api)
|
|
207
|
+
password: string;
|
|
208
|
+
|
|
209
|
+
@OnSerialize((value) => String(value) + "test")
|
|
210
|
+
@Name("mapped_prop")
|
|
211
|
+
mappedProp: string;
|
|
212
|
+
|
|
213
|
+
@CollectionOf(Role)
|
|
214
|
+
roles: Role[] = [];
|
|
215
|
+
|
|
216
|
+
@CollectionOf(Role)
|
|
217
|
+
mapRoles: Map<string, Role> = new Map();
|
|
218
|
+
|
|
219
|
+
@CollectionOf(String)
|
|
220
|
+
setRoleNames: Set<string> = new Set();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const model = new Model();
|
|
224
|
+
model.id = "id";
|
|
225
|
+
model.password = "string";
|
|
226
|
+
model.mappedProp = "mappedProp";
|
|
227
|
+
model.roles = [new Role({label: "ADMIN"})];
|
|
228
|
+
|
|
229
|
+
model.mapRoles = new Map([["ADMIN", new Role({label: "ADMIN"})]]);
|
|
230
|
+
|
|
231
|
+
model.setRoleNames = new Set();
|
|
232
|
+
model.setRoleNames.add("ADMIN");
|
|
233
|
+
|
|
234
|
+
expect(
|
|
235
|
+
serialize(model, {
|
|
236
|
+
useAlias: false,
|
|
237
|
+
api: true
|
|
238
|
+
})
|
|
239
|
+
).toEqual({
|
|
240
|
+
id: "id",
|
|
241
|
+
mapRoles: {
|
|
242
|
+
ADMIN: {
|
|
243
|
+
label: "ADMIN"
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
mappedProp: "mappedProptest",
|
|
247
|
+
roles: [
|
|
248
|
+
{
|
|
249
|
+
label: "ADMIN"
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
setRoleNames: ["ADMIN"]
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
it("should serialize model (api = false)", () => {
|
|
256
|
+
class Role {
|
|
257
|
+
@Property()
|
|
258
|
+
label: string;
|
|
259
|
+
|
|
260
|
+
constructor({label}: any = {}) {
|
|
261
|
+
this.label = label;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
class Model {
|
|
266
|
+
@Property()
|
|
267
|
+
id: string;
|
|
268
|
+
|
|
269
|
+
@Ignore((ignored, ctx: JsonHookContext) => ctx.api)
|
|
270
|
+
password: string;
|
|
271
|
+
|
|
272
|
+
@OnSerialize((value) => String(value) + "test")
|
|
273
|
+
@Name("mapped_prop")
|
|
274
|
+
mappedProp: string;
|
|
275
|
+
|
|
276
|
+
@CollectionOf(Role)
|
|
277
|
+
roles: Role[] = [];
|
|
278
|
+
|
|
279
|
+
@CollectionOf(Role)
|
|
280
|
+
mapRoles: Map<string, Role> = new Map();
|
|
281
|
+
|
|
282
|
+
@CollectionOf(String)
|
|
283
|
+
setRoleNames: Set<string> = new Set();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const model = new Model();
|
|
287
|
+
model.id = "id";
|
|
288
|
+
model.password = "string";
|
|
289
|
+
model.mappedProp = "mappedProp";
|
|
290
|
+
model.roles = [new Role({label: "ADMIN"})];
|
|
291
|
+
|
|
292
|
+
model.mapRoles = new Map([["ADMIN", new Role({label: "ADMIN"})]]);
|
|
293
|
+
|
|
294
|
+
model.setRoleNames = new Set();
|
|
295
|
+
model.setRoleNames.add("ADMIN");
|
|
296
|
+
|
|
297
|
+
expect(
|
|
298
|
+
serialize(model, {
|
|
299
|
+
useAlias: false,
|
|
300
|
+
api: false
|
|
301
|
+
})
|
|
302
|
+
).toEqual({
|
|
303
|
+
id: "id",
|
|
304
|
+
mapRoles: {
|
|
305
|
+
ADMIN: {
|
|
306
|
+
label: "ADMIN"
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
password: "string",
|
|
310
|
+
mappedProp: "mappedProptest",
|
|
311
|
+
roles: [
|
|
312
|
+
{
|
|
313
|
+
label: "ADMIN"
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
setRoleNames: ["ADMIN"]
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
it("should serialize model and take the right type", () => {
|
|
321
|
+
class Role {
|
|
322
|
+
@Property()
|
|
323
|
+
label: string;
|
|
324
|
+
|
|
325
|
+
constructor({label}: any = {}) {
|
|
326
|
+
this.label = label;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
class Model {
|
|
331
|
+
@Property()
|
|
332
|
+
id: string;
|
|
333
|
+
|
|
334
|
+
@Ignore((ignored, ctx: JsonHookContext) => ctx.api)
|
|
335
|
+
password: string;
|
|
336
|
+
|
|
337
|
+
@OnSerialize((value) => String(value) + "test")
|
|
338
|
+
@Name("mapped_prop")
|
|
339
|
+
mappedProp: string;
|
|
340
|
+
|
|
341
|
+
@CollectionOf(Role)
|
|
342
|
+
roles: Map<string, Role> = new Map();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
class ServerResponse {
|
|
346
|
+
@Property()
|
|
347
|
+
data: Model;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const model = new Model();
|
|
351
|
+
model.id = "id";
|
|
352
|
+
model.password = "hellopassword";
|
|
353
|
+
model.mappedProp = "hello";
|
|
354
|
+
model.roles.set("olo", new Role({label: "label"}));
|
|
355
|
+
|
|
356
|
+
expect(serialize(model, {type: ServerResponse})).toEqual({
|
|
357
|
+
id: "id",
|
|
358
|
+
password: "hellopassword",
|
|
359
|
+
mapped_prop: "hellotest",
|
|
360
|
+
roles: {
|
|
361
|
+
olo: {
|
|
362
|
+
label: "label"
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
it("should serialize model with alias property", () => {
|
|
368
|
+
class Role {
|
|
369
|
+
@Property()
|
|
370
|
+
label: string;
|
|
371
|
+
|
|
372
|
+
constructor({label}: any = {}) {
|
|
373
|
+
this.label = label;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
class Model {
|
|
378
|
+
@Property()
|
|
379
|
+
id: string;
|
|
380
|
+
|
|
381
|
+
@Ignore((ignored, ctx: JsonHookContext) => ctx.api)
|
|
382
|
+
password: string;
|
|
383
|
+
|
|
384
|
+
@OnSerialize((value) => String(value) + "test")
|
|
385
|
+
@Name("mapped_prop")
|
|
386
|
+
mappedProp: string;
|
|
387
|
+
|
|
388
|
+
@CollectionOf(Role)
|
|
389
|
+
roles: Role[] = [];
|
|
390
|
+
|
|
391
|
+
@CollectionOf(Role)
|
|
392
|
+
mapRoles: Map<string, Role> = new Map();
|
|
393
|
+
|
|
394
|
+
@CollectionOf(String)
|
|
395
|
+
setRoleNames: Set<string> = new Set();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const model = new Model();
|
|
399
|
+
model.id = "id";
|
|
400
|
+
model.password = "string";
|
|
401
|
+
model.mappedProp = "mappedProp";
|
|
402
|
+
model.roles = [new Role({label: "ADMIN"})];
|
|
403
|
+
|
|
404
|
+
model.mapRoles = new Map([["ADMIN", new Role({label: "ADMIN"})]]);
|
|
405
|
+
|
|
406
|
+
model.setRoleNames = new Set();
|
|
407
|
+
model.setRoleNames.add("ADMIN");
|
|
408
|
+
|
|
409
|
+
expect(
|
|
410
|
+
serialize(model, {
|
|
411
|
+
useAlias: true,
|
|
412
|
+
api: false
|
|
413
|
+
})
|
|
414
|
+
).toEqual({
|
|
415
|
+
id: "id",
|
|
416
|
+
mapRoles: {
|
|
417
|
+
ADMIN: {
|
|
418
|
+
label: "ADMIN"
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
mapped_prop: "mappedProptest",
|
|
422
|
+
password: "string",
|
|
423
|
+
roles: [
|
|
424
|
+
{
|
|
425
|
+
label: "ADMIN"
|
|
426
|
+
}
|
|
427
|
+
],
|
|
428
|
+
setRoleNames: ["ADMIN"]
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
it("should serialize model with nested object", () => {
|
|
432
|
+
class Nested {
|
|
433
|
+
@Property()
|
|
434
|
+
label: string;
|
|
435
|
+
|
|
436
|
+
@Name("additional_description")
|
|
437
|
+
additionalDescription: string;
|
|
438
|
+
|
|
439
|
+
constructor({label, additionalDescription}: any = {}) {
|
|
440
|
+
this.label = label;
|
|
441
|
+
this.additionalDescription = additionalDescription;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
class Model {
|
|
446
|
+
@Property()
|
|
447
|
+
id: string;
|
|
448
|
+
|
|
449
|
+
@Property()
|
|
450
|
+
nested: any;
|
|
451
|
+
|
|
452
|
+
@Property()
|
|
453
|
+
nestedTyped: Nested;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const model = new Model();
|
|
457
|
+
model.id = "id";
|
|
458
|
+
model.nested = {
|
|
459
|
+
other: "other",
|
|
460
|
+
test: new Nested({
|
|
461
|
+
additionalDescription: "additionalDescription",
|
|
462
|
+
label: "label"
|
|
463
|
+
})
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
model.nestedTyped = new Nested({
|
|
467
|
+
additionalDescription: "additionalDescription",
|
|
468
|
+
label: "label"
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
expect(
|
|
472
|
+
serialize(model, {
|
|
473
|
+
useAlias: true,
|
|
474
|
+
api: false
|
|
475
|
+
})
|
|
476
|
+
).toEqual({
|
|
477
|
+
id: "id",
|
|
478
|
+
nested: {
|
|
479
|
+
other: "other",
|
|
480
|
+
test: {
|
|
481
|
+
additional_description: "additionalDescription",
|
|
482
|
+
label: "label"
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
nestedTyped: {
|
|
486
|
+
additional_description: "additionalDescription",
|
|
487
|
+
label: "label"
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
it("should serialize model with additional properties", () => {
|
|
492
|
+
@AdditionalProperties(true)
|
|
493
|
+
class Model {
|
|
494
|
+
@Property()
|
|
495
|
+
id: string;
|
|
496
|
+
|
|
497
|
+
@OnSerialize((value) => String(value) + "test")
|
|
498
|
+
@Name("mapped_prop")
|
|
499
|
+
mappedProp: string;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
expect(
|
|
503
|
+
serialize(
|
|
504
|
+
{
|
|
505
|
+
id: "id",
|
|
506
|
+
mappedProp: "mappedProp",
|
|
507
|
+
additionalProperty: true
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
type: Model,
|
|
511
|
+
useAlias: false
|
|
512
|
+
}
|
|
513
|
+
)
|
|
514
|
+
).toEqual({
|
|
515
|
+
additionalProperty: true,
|
|
516
|
+
id: "id",
|
|
517
|
+
mappedProp: "mappedProptest"
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
it("should serialize model (recursive class)", () => {
|
|
521
|
+
class User {
|
|
522
|
+
@Property()
|
|
523
|
+
name: string;
|
|
524
|
+
|
|
525
|
+
@CollectionOf(() => Post)
|
|
526
|
+
posts: any[];
|
|
527
|
+
|
|
528
|
+
@Property(() => Post)
|
|
529
|
+
@Name("main_post")
|
|
530
|
+
mainPost: any;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
class Post {
|
|
534
|
+
@Property()
|
|
535
|
+
id: string;
|
|
536
|
+
|
|
537
|
+
@Property()
|
|
538
|
+
owner: User;
|
|
539
|
+
|
|
540
|
+
@Name("title")
|
|
541
|
+
initializedTitle: string;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const post = new Post();
|
|
545
|
+
post.id = "id";
|
|
546
|
+
|
|
547
|
+
post.owner = new User();
|
|
548
|
+
post.owner.name = "name";
|
|
549
|
+
post.owner.posts = [new Post()];
|
|
550
|
+
post.owner.posts[0].id = "id";
|
|
551
|
+
post.owner.posts[0].initializedTitle = "initializedTitle";
|
|
552
|
+
|
|
553
|
+
post.owner.mainPost = new Post();
|
|
554
|
+
post.owner.mainPost.id = "idMain";
|
|
555
|
+
post.owner.mainPost.initializedTitle = "initializedTitle";
|
|
556
|
+
|
|
557
|
+
const result = serialize(post, {useAlias: true});
|
|
558
|
+
|
|
559
|
+
expect(result).toEqual({
|
|
560
|
+
id: "id",
|
|
561
|
+
owner: {
|
|
562
|
+
name: "name",
|
|
563
|
+
main_post: {
|
|
564
|
+
id: "idMain",
|
|
565
|
+
title: "initializedTitle"
|
|
566
|
+
},
|
|
567
|
+
posts: [
|
|
568
|
+
{
|
|
569
|
+
id: "id",
|
|
570
|
+
title: "initializedTitle"
|
|
571
|
+
}
|
|
572
|
+
]
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
it("should serialize model without props", () => {
|
|
577
|
+
class Test {
|
|
578
|
+
raw: any;
|
|
579
|
+
affected?: number | null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const t = new Test();
|
|
583
|
+
|
|
584
|
+
t.raw = 1;
|
|
585
|
+
t.affected = 1;
|
|
586
|
+
|
|
587
|
+
const result = serialize(t, {type: Object});
|
|
588
|
+
|
|
589
|
+
expect(result).toEqual({
|
|
590
|
+
affected: 1,
|
|
591
|
+
raw: 1
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
it("should discover property dynamically when any schema decorators are used", () => {
|
|
595
|
+
function log(): PropertyDecorator {
|
|
596
|
+
return () => {};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
class User {
|
|
600
|
+
@log()
|
|
601
|
+
id: string;
|
|
602
|
+
|
|
603
|
+
test: string;
|
|
604
|
+
|
|
605
|
+
[key: string]: any;
|
|
606
|
+
|
|
607
|
+
constructor({id, test}: any = {}) {
|
|
608
|
+
this.id = id;
|
|
609
|
+
test && (this.test = test);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const user1 = new User({id: "id"});
|
|
614
|
+
const user2 = new User({id: "id", test: "test"});
|
|
615
|
+
const user3 = new User({id: "id", test: "test"});
|
|
616
|
+
|
|
617
|
+
user3.extra = "extra";
|
|
618
|
+
|
|
619
|
+
expect(serialize(user1, {type: User})).toEqual({id: "id"});
|
|
620
|
+
expect(serialize(user2, {type: User})).toEqual({id: "id", test: "test"});
|
|
621
|
+
expect(serialize(user3, {type: User})).toEqual({
|
|
622
|
+
extra: "extra",
|
|
623
|
+
id: "id",
|
|
624
|
+
test: "test"
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
it("should serialize model with null values", () => {
|
|
628
|
+
class NestedModel {
|
|
629
|
+
@Property()
|
|
630
|
+
id: string;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
class NullModel {
|
|
634
|
+
@Property()
|
|
635
|
+
prop1: string;
|
|
636
|
+
|
|
637
|
+
@Property()
|
|
638
|
+
prop2: number;
|
|
639
|
+
|
|
640
|
+
@Property()
|
|
641
|
+
prop3: Date;
|
|
642
|
+
|
|
643
|
+
@Nullable(NestedModel)
|
|
644
|
+
prop4: NestedModel;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
expect(
|
|
648
|
+
serialize(
|
|
649
|
+
{
|
|
650
|
+
prop1: null,
|
|
651
|
+
prop2: null,
|
|
652
|
+
prop3: null,
|
|
653
|
+
prop4: null
|
|
654
|
+
},
|
|
655
|
+
{type: NullModel}
|
|
656
|
+
)
|
|
657
|
+
).toEqual({
|
|
658
|
+
prop1: null,
|
|
659
|
+
prop2: null,
|
|
660
|
+
prop3: null,
|
|
661
|
+
prop4: null
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
it("should transform keep array, map, set without nullable value", () => {
|
|
665
|
+
class Model {
|
|
666
|
+
@CollectionOf(String)
|
|
667
|
+
public bars?: string[];
|
|
668
|
+
|
|
669
|
+
@CollectionOf(String)
|
|
670
|
+
public barsSet?: Set<string>;
|
|
671
|
+
|
|
672
|
+
@CollectionOf(String)
|
|
673
|
+
public barsMap?: Map<string, string>;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const payload = {
|
|
677
|
+
bars: null,
|
|
678
|
+
barsSet: null,
|
|
679
|
+
barsMap: null
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
const result = serialize(payload, {type: Model});
|
|
683
|
+
|
|
684
|
+
expect(result).toEqual({
|
|
685
|
+
bars: null,
|
|
686
|
+
barsSet: null,
|
|
687
|
+
barsMap: null
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
it("should serialize array model with alias property", () => {
|
|
691
|
+
class SpaBooking {
|
|
692
|
+
@Required()
|
|
693
|
+
@Name("booking_number")
|
|
694
|
+
bookingNumber: string;
|
|
695
|
+
|
|
696
|
+
@Required()
|
|
697
|
+
status: string;
|
|
698
|
+
|
|
699
|
+
@Required()
|
|
700
|
+
@Name("order_id")
|
|
701
|
+
orderId: number;
|
|
702
|
+
|
|
703
|
+
@Required()
|
|
704
|
+
@Name("appointment_id")
|
|
705
|
+
appointmentId: number;
|
|
706
|
+
|
|
707
|
+
@Name("customer_id")
|
|
708
|
+
@Groups("!create", "!read")
|
|
709
|
+
customerId: number;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const appointments = [
|
|
713
|
+
{
|
|
714
|
+
bookingNumber: "100566224434",
|
|
715
|
+
status: "Booked",
|
|
716
|
+
orderId: 711376505,
|
|
717
|
+
appointmentId: 566224434,
|
|
718
|
+
customerId: null
|
|
719
|
+
}
|
|
720
|
+
];
|
|
721
|
+
|
|
722
|
+
const serializedResult = serialize(appointments, {
|
|
723
|
+
type: SpaBooking
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
expect(serializedResult).toEqual([
|
|
727
|
+
{
|
|
728
|
+
booking_number: "100566224434",
|
|
729
|
+
status: "Booked",
|
|
730
|
+
order_id: 711376505,
|
|
731
|
+
appointment_id: 566224434,
|
|
732
|
+
customer_id: null
|
|
733
|
+
}
|
|
734
|
+
]);
|
|
735
|
+
});
|
|
736
|
+
it("should serialize model without decorator", () => {
|
|
737
|
+
class Client {
|
|
738
|
+
clientId: string;
|
|
739
|
+
clientSecret: string;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const client = new Client();
|
|
743
|
+
client.clientId = "id";
|
|
744
|
+
client.clientSecret = "secret";
|
|
745
|
+
|
|
746
|
+
const result = serialize(client, {useAlias: false, groups: ["render"]});
|
|
747
|
+
|
|
748
|
+
expect(result === client).toBeFalsy();
|
|
749
|
+
expect(result).toEqual({
|
|
750
|
+
clientId: "id",
|
|
751
|
+
clientSecret: "secret"
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
describe("class with toJSON/toClass", () => {
|
|
756
|
+
it("should serialize model", () => {
|
|
757
|
+
class Role {
|
|
758
|
+
@Property()
|
|
759
|
+
label: string;
|
|
760
|
+
|
|
761
|
+
constructor({label}: any = {}) {
|
|
762
|
+
this.label = label;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
class Model {
|
|
767
|
+
@Property()
|
|
768
|
+
id: string;
|
|
769
|
+
|
|
770
|
+
@Ignore((ignored, ctx: JsonHookContext) => ctx.api)
|
|
771
|
+
password: string;
|
|
772
|
+
|
|
773
|
+
@OnSerialize((value) => String(value) + "test")
|
|
774
|
+
@Name("mapped_prop")
|
|
775
|
+
mappedProp: string;
|
|
776
|
+
|
|
777
|
+
@CollectionOf(Role)
|
|
778
|
+
roles: Map<string, Role> = new Map();
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const model = new Model();
|
|
782
|
+
// @ts-ignore
|
|
783
|
+
model.$isMongooseModelPrototype = true;
|
|
784
|
+
// @ts-ignore
|
|
785
|
+
model["toJSON"] = (options: any) => {
|
|
786
|
+
const result = deserialize(
|
|
787
|
+
{
|
|
788
|
+
id: "id",
|
|
789
|
+
password: "hellopassword",
|
|
790
|
+
mappedProp: "hello"
|
|
791
|
+
},
|
|
792
|
+
{useAlias: false, type: Model}
|
|
793
|
+
);
|
|
794
|
+
|
|
795
|
+
return serialize(result, options);
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
expect(serialize(model, {type: Model})).toEqual({
|
|
799
|
+
id: "id",
|
|
800
|
+
mapped_prop: "hellotest",
|
|
801
|
+
password: "hellopassword",
|
|
802
|
+
roles: {}
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
expect(serialize(model, {api: true, useAlias: false})).toEqual({
|
|
806
|
+
id: "id",
|
|
807
|
+
mappedProp: "hellotest",
|
|
808
|
+
roles: {}
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
expect(serialize([model], {type: Model})).toEqual([
|
|
812
|
+
{
|
|
813
|
+
id: "id",
|
|
814
|
+
mapped_prop: "hellotest",
|
|
815
|
+
password: "hellopassword",
|
|
816
|
+
roles: {}
|
|
817
|
+
}
|
|
818
|
+
]);
|
|
819
|
+
});
|
|
820
|
+
it("should serialize model (protected keyword)", () => {
|
|
821
|
+
class Role {
|
|
822
|
+
@Property()
|
|
823
|
+
label: string;
|
|
824
|
+
|
|
825
|
+
constructor({label}: any = {}) {
|
|
826
|
+
this.label = label;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
class Model {
|
|
831
|
+
@Property()
|
|
832
|
+
id: string;
|
|
833
|
+
|
|
834
|
+
@Ignore((ignored, ctx: JsonHookContext) => ctx.api)
|
|
835
|
+
password: string;
|
|
836
|
+
|
|
837
|
+
@OnSerialize((value) => String(value) + "test")
|
|
838
|
+
@Name("mapped_prop")
|
|
839
|
+
mappedProp: string;
|
|
840
|
+
|
|
841
|
+
@Property()
|
|
842
|
+
enum: string;
|
|
843
|
+
|
|
844
|
+
@CollectionOf(Role)
|
|
845
|
+
roles: Map<string, Role> = new Map();
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const model = new Model();
|
|
849
|
+
// @ts-ignore
|
|
850
|
+
model.$isMongooseModelPrototype = true;
|
|
851
|
+
// @ts-ignore
|
|
852
|
+
model["toJSON"] = (options: any) => {
|
|
853
|
+
const result = deserialize(
|
|
854
|
+
{
|
|
855
|
+
id: "id",
|
|
856
|
+
password: "hellopassword",
|
|
857
|
+
mappedProp: "hello",
|
|
858
|
+
enum: "test"
|
|
859
|
+
},
|
|
860
|
+
{useAlias: false, type: Model}
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
return serialize(result, options);
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
expect(serialize(model, {type: Model})).toEqual({
|
|
867
|
+
id: "id",
|
|
868
|
+
enum: "test",
|
|
869
|
+
mapped_prop: "hellotest",
|
|
870
|
+
password: "hellopassword",
|
|
871
|
+
roles: {}
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
expect(serialize(model, {api: true, useAlias: false})).toEqual({
|
|
875
|
+
id: "id",
|
|
876
|
+
enum: "test",
|
|
877
|
+
mappedProp: "hellotest",
|
|
878
|
+
roles: {}
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
expect(serialize([model], {type: Model})).toEqual([
|
|
882
|
+
{
|
|
883
|
+
id: "id",
|
|
884
|
+
enum: "test",
|
|
885
|
+
mapped_prop: "hellotest",
|
|
886
|
+
password: "hellopassword",
|
|
887
|
+
roles: {}
|
|
888
|
+
}
|
|
889
|
+
]);
|
|
890
|
+
});
|
|
891
|
+
it("should serialize model Array", () => {
|
|
892
|
+
class Role {
|
|
893
|
+
@Property()
|
|
894
|
+
label: string;
|
|
895
|
+
|
|
896
|
+
constructor({label}: any = {}) {
|
|
897
|
+
this.label = label;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
class Model {
|
|
902
|
+
@Property()
|
|
903
|
+
id: string;
|
|
904
|
+
|
|
905
|
+
@Ignore((ignored, ctx: JsonHookContext) => ctx.api)
|
|
906
|
+
password: string;
|
|
907
|
+
|
|
908
|
+
@OnSerialize((value) => String(value) + "test")
|
|
909
|
+
@Name("mapped_prop")
|
|
910
|
+
mappedProp: string;
|
|
911
|
+
|
|
912
|
+
@CollectionOf(Role)
|
|
913
|
+
roles: Map<string, Role> = new Map();
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const model = new Model();
|
|
917
|
+
model.id = "id";
|
|
918
|
+
model.password = "hellopassword";
|
|
919
|
+
model.mappedProp = "hello";
|
|
920
|
+
model.roles.set("olo", new Role({label: "label"}));
|
|
921
|
+
|
|
922
|
+
expect(serialize([model], {type: Model})).toEqual([
|
|
923
|
+
{
|
|
924
|
+
id: "id",
|
|
925
|
+
password: "hellopassword",
|
|
926
|
+
mapped_prop: "hellotest",
|
|
927
|
+
roles: {
|
|
928
|
+
olo: {
|
|
929
|
+
label: "label"
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
]);
|
|
934
|
+
});
|
|
935
|
+
it("should serialize model with nested model and not populated data (mongoose)", () => {
|
|
936
|
+
class Workspace {
|
|
937
|
+
@Property()
|
|
938
|
+
_id: string;
|
|
939
|
+
|
|
940
|
+
@Property()
|
|
941
|
+
name: string;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
class MyWorkspace {
|
|
945
|
+
@Property()
|
|
946
|
+
workspaceId: Workspace;
|
|
947
|
+
|
|
948
|
+
@Property()
|
|
949
|
+
title: string;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
class UserWorkspace {
|
|
953
|
+
@Property()
|
|
954
|
+
_id: string;
|
|
955
|
+
|
|
956
|
+
@CollectionOf(MyWorkspace)
|
|
957
|
+
workspaces: MyWorkspace[];
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const userWorkspace = new UserWorkspace();
|
|
961
|
+
userWorkspace._id = new ObjectId("64e061ba7356daf00a66c130") as unknown as string;
|
|
962
|
+
userWorkspace.workspaces = [new MyWorkspace()];
|
|
963
|
+
userWorkspace.workspaces[0].title = "MyTest";
|
|
964
|
+
userWorkspace.workspaces[0].workspaceId = new ObjectId("64e061ba7356daf00a66c130") as unknown as Workspace;
|
|
965
|
+
|
|
966
|
+
expect(serialize(userWorkspace, {type: UserWorkspace})).toEqual({
|
|
967
|
+
_id: "64e061ba7356daf00a66c130",
|
|
968
|
+
workspaces: [
|
|
969
|
+
{
|
|
970
|
+
title: "MyTest",
|
|
971
|
+
workspaceId: "64e061ba7356daf00a66c130"
|
|
972
|
+
}
|
|
973
|
+
]
|
|
974
|
+
});
|
|
975
|
+
});
|
|
976
|
+
it("should serialize model with nested model and not populated data (Ref mongoose)", () => {
|
|
977
|
+
class TestUser {
|
|
978
|
+
@Required()
|
|
979
|
+
email: string;
|
|
980
|
+
|
|
981
|
+
@Required()
|
|
982
|
+
@MinLength(6)
|
|
983
|
+
@Groups("creation")
|
|
984
|
+
password: string;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
class TestProfile {
|
|
988
|
+
@OnSerialize((value, ctx) => {
|
|
989
|
+
if (isObjectID(value)) {
|
|
990
|
+
return value.toString();
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return serialize(value, {...ctx, type: TestUser});
|
|
994
|
+
})
|
|
995
|
+
user: any;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const profile = new TestProfile();
|
|
999
|
+
profile.user = new ObjectId("64e061ba7356daf00a66c130");
|
|
1000
|
+
|
|
1001
|
+
expect(serialize([profile])).toEqual([
|
|
1002
|
+
{
|
|
1003
|
+
user: "64e061ba7356daf00a66c130"
|
|
1004
|
+
}
|
|
1005
|
+
]);
|
|
1006
|
+
});
|
|
1007
|
+
it("should serialize model (inherited class)", () => {
|
|
1008
|
+
class Role {
|
|
1009
|
+
@Property()
|
|
1010
|
+
label: string;
|
|
1011
|
+
|
|
1012
|
+
constructor({label}: any = {}) {
|
|
1013
|
+
this.label = label;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
class Base {
|
|
1018
|
+
@CollectionOf(Role)
|
|
1019
|
+
roles: Map<string, Role> = new Map();
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
class Model extends Base {
|
|
1023
|
+
@Property()
|
|
1024
|
+
id: string;
|
|
1025
|
+
|
|
1026
|
+
@Ignore((ignored, ctx: JsonHookContext) => ctx.api)
|
|
1027
|
+
password: string;
|
|
1028
|
+
|
|
1029
|
+
@OnSerialize((value) => String(value) + "test")
|
|
1030
|
+
@Name("mapped_prop")
|
|
1031
|
+
mappedProp: string;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const model = new Model();
|
|
1035
|
+
model.id = "id";
|
|
1036
|
+
model.password = "hellopassword";
|
|
1037
|
+
model.mappedProp = "hello";
|
|
1038
|
+
model.roles.set("olo", new Role({label: "label"}));
|
|
1039
|
+
|
|
1040
|
+
expect(serialize(model)).toEqual({
|
|
1041
|
+
id: "id",
|
|
1042
|
+
password: "hellopassword",
|
|
1043
|
+
mapped_prop: "hellotest",
|
|
1044
|
+
roles: {
|
|
1045
|
+
olo: {
|
|
1046
|
+
label: "label"
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
expect(serialize(model, {api: true, useAlias: false})).toEqual({
|
|
1052
|
+
id: "id",
|
|
1053
|
+
mappedProp: "hellotest",
|
|
1054
|
+
roles: {
|
|
1055
|
+
olo: {
|
|
1056
|
+
label: "label"
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
});
|
|
1061
|
+
it("should serialize model (recursive class)", () => {
|
|
1062
|
+
const post = new Post();
|
|
1063
|
+
post.id = "id";
|
|
1064
|
+
post.owner = new User();
|
|
1065
|
+
post.owner.name = "name";
|
|
1066
|
+
post.owner.posts = [new Post()];
|
|
1067
|
+
post.owner.posts[0].id = "id";
|
|
1068
|
+
|
|
1069
|
+
const result = serialize(post);
|
|
1070
|
+
|
|
1071
|
+
expect(result).toEqual({
|
|
1072
|
+
id: "id",
|
|
1073
|
+
owner: {
|
|
1074
|
+
name: "name",
|
|
1075
|
+
posts: [
|
|
1076
|
+
{
|
|
1077
|
+
id: "id"
|
|
1078
|
+
}
|
|
1079
|
+
]
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
});
|
|
1083
|
+
it("should serialize model with object props", () => {
|
|
1084
|
+
class Model {
|
|
1085
|
+
@Property()
|
|
1086
|
+
test: any;
|
|
1087
|
+
|
|
1088
|
+
@Property()
|
|
1089
|
+
id: string;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
const test = new Model();
|
|
1093
|
+
test.id = "id";
|
|
1094
|
+
test.test = {
|
|
1095
|
+
value: "test",
|
|
1096
|
+
nullProp: null
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
const result = serialize(test, {type: Model});
|
|
1100
|
+
|
|
1101
|
+
expect(result).toEqual({
|
|
1102
|
+
id: "id",
|
|
1103
|
+
test: {
|
|
1104
|
+
value: "test",
|
|
1105
|
+
nullProp: null
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
});
|
|
1109
|
+
it("should serialize model with additional object props", () => {
|
|
1110
|
+
@AdditionalProperties(true)
|
|
1111
|
+
class Model {
|
|
1112
|
+
@Property()
|
|
1113
|
+
id: string;
|
|
1114
|
+
|
|
1115
|
+
@Ignore(true)
|
|
1116
|
+
ignored: boolean;
|
|
1117
|
+
|
|
1118
|
+
@Name("renamed")
|
|
1119
|
+
name: string;
|
|
1120
|
+
|
|
1121
|
+
[type: string]: any;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const test = new Model();
|
|
1125
|
+
test.id = "id";
|
|
1126
|
+
test.ignored = true;
|
|
1127
|
+
test.name = "myname";
|
|
1128
|
+
test.additional = {foo: "bar"};
|
|
1129
|
+
|
|
1130
|
+
const result = serialize(test, {type: Model});
|
|
1131
|
+
|
|
1132
|
+
expect(result).toEqual({
|
|
1133
|
+
id: "id",
|
|
1134
|
+
renamed: "myname",
|
|
1135
|
+
additional: {
|
|
1136
|
+
foo: "bar"
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
it("should not serialize model with additional object props", () => {
|
|
1141
|
+
class Model {
|
|
1142
|
+
@Property()
|
|
1143
|
+
id: string;
|
|
1144
|
+
|
|
1145
|
+
@Ignore(true)
|
|
1146
|
+
ignored: boolean;
|
|
1147
|
+
|
|
1148
|
+
@Name("renamed")
|
|
1149
|
+
name: string;
|
|
1150
|
+
|
|
1151
|
+
[type: string]: any;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const test = new Model();
|
|
1155
|
+
test.id = "id";
|
|
1156
|
+
test.ignored = true;
|
|
1157
|
+
test.name = "myname";
|
|
1158
|
+
test.additional = {foo: "bar"};
|
|
1159
|
+
|
|
1160
|
+
const result = serialize(test, {type: Model});
|
|
1161
|
+
|
|
1162
|
+
expect(result).toEqual({
|
|
1163
|
+
id: "id",
|
|
1164
|
+
renamed: "myname"
|
|
1165
|
+
});
|
|
1166
|
+
});
|
|
1167
|
+
it("should serialize model to object (with default value - no assigned)", () => {
|
|
1168
|
+
class SpaCareCategory {
|
|
1169
|
+
@Required()
|
|
1170
|
+
@Groups("!details")
|
|
1171
|
+
id: string;
|
|
1172
|
+
|
|
1173
|
+
@Required()
|
|
1174
|
+
label: string;
|
|
1175
|
+
|
|
1176
|
+
@Required()
|
|
1177
|
+
@OnDeserialize((name: string) => snakeCase(name).toUpperCase())
|
|
1178
|
+
code: string;
|
|
1179
|
+
|
|
1180
|
+
@Required()
|
|
1181
|
+
@Default(0)
|
|
1182
|
+
@OnSerialize((o) => {
|
|
1183
|
+
return o || 0;
|
|
1184
|
+
})
|
|
1185
|
+
weight: number = 0;
|
|
1186
|
+
|
|
1187
|
+
constructor({id, label, code, weight}: Partial<SpaCareCategory> = {}) {
|
|
1188
|
+
Object.assign(
|
|
1189
|
+
this,
|
|
1190
|
+
cleanObject({
|
|
1191
|
+
id,
|
|
1192
|
+
label,
|
|
1193
|
+
code,
|
|
1194
|
+
weight
|
|
1195
|
+
})
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
expect(
|
|
1201
|
+
serialize(
|
|
1202
|
+
{
|
|
1203
|
+
label: "categoryLabel",
|
|
1204
|
+
code: "CATEGORY_CODE"
|
|
1205
|
+
},
|
|
1206
|
+
{type: SpaCareCategory}
|
|
1207
|
+
)
|
|
1208
|
+
).toEqual({
|
|
1209
|
+
code: "CATEGORY_CODE",
|
|
1210
|
+
label: "categoryLabel",
|
|
1211
|
+
weight: 0
|
|
1212
|
+
});
|
|
1213
|
+
});
|
|
1214
|
+
it("should serialize model to object (with default value - no assigned - custom decorator)", () => {
|
|
1215
|
+
function AllowEmpty() {
|
|
1216
|
+
return useDecorators(
|
|
1217
|
+
Default(""),
|
|
1218
|
+
Allow(""),
|
|
1219
|
+
OnDeserialize((o: any) => (o === null || o === undefined ? "" : o)),
|
|
1220
|
+
OnSerialize((o: any) => (o === null || o === undefined ? "" : o))
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
class SpaInformation {
|
|
1225
|
+
@Required()
|
|
1226
|
+
id: number;
|
|
1227
|
+
|
|
1228
|
+
@Required()
|
|
1229
|
+
@AllowEmpty()
|
|
1230
|
+
label: string = "";
|
|
1231
|
+
|
|
1232
|
+
@Required()
|
|
1233
|
+
@AllowEmpty()
|
|
1234
|
+
description: string = "";
|
|
1235
|
+
|
|
1236
|
+
@AllowEmpty()
|
|
1237
|
+
currency: string = "EUR";
|
|
1238
|
+
|
|
1239
|
+
@Email()
|
|
1240
|
+
@AllowEmpty()
|
|
1241
|
+
email: string;
|
|
1242
|
+
|
|
1243
|
+
@AllowEmpty()
|
|
1244
|
+
phone: string;
|
|
1245
|
+
|
|
1246
|
+
@Required()
|
|
1247
|
+
@Name("are_children_accepted")
|
|
1248
|
+
areChildrenAccepted: boolean = false;
|
|
1249
|
+
|
|
1250
|
+
@AllowEmpty()
|
|
1251
|
+
website: string = "";
|
|
1252
|
+
|
|
1253
|
+
@Uri()
|
|
1254
|
+
@AllowEmpty()
|
|
1255
|
+
logo: string;
|
|
1256
|
+
|
|
1257
|
+
@Uri()
|
|
1258
|
+
@AllowEmpty()
|
|
1259
|
+
image: string;
|
|
1260
|
+
|
|
1261
|
+
@AllowEmpty()
|
|
1262
|
+
location: string = "";
|
|
1263
|
+
|
|
1264
|
+
@Name("cancellation_hours_limit")
|
|
1265
|
+
@Nullable(Number)
|
|
1266
|
+
@OnSerialize((o) => (isNumber(o) ? o : null))
|
|
1267
|
+
cancellationHoursLimit: number | null = null;
|
|
1268
|
+
|
|
1269
|
+
constructor({
|
|
1270
|
+
id,
|
|
1271
|
+
label,
|
|
1272
|
+
description,
|
|
1273
|
+
currency,
|
|
1274
|
+
email,
|
|
1275
|
+
phone,
|
|
1276
|
+
areChildrenAccepted,
|
|
1277
|
+
website,
|
|
1278
|
+
logo,
|
|
1279
|
+
image,
|
|
1280
|
+
location,
|
|
1281
|
+
cancellationHoursLimit = null
|
|
1282
|
+
}: Partial<SpaInformation> = {}) {
|
|
1283
|
+
Object.assign(
|
|
1284
|
+
this,
|
|
1285
|
+
cleanObject({
|
|
1286
|
+
id,
|
|
1287
|
+
label,
|
|
1288
|
+
description,
|
|
1289
|
+
currency,
|
|
1290
|
+
email,
|
|
1291
|
+
phone,
|
|
1292
|
+
areChildrenAccepted,
|
|
1293
|
+
website,
|
|
1294
|
+
logo,
|
|
1295
|
+
image,
|
|
1296
|
+
location,
|
|
1297
|
+
cancellationHoursLimit
|
|
1298
|
+
})
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const result = serialize(
|
|
1304
|
+
{
|
|
1305
|
+
id: 453,
|
|
1306
|
+
address: null,
|
|
1307
|
+
label: null,
|
|
1308
|
+
currency: null,
|
|
1309
|
+
description: null,
|
|
1310
|
+
email: null,
|
|
1311
|
+
phone: undefined,
|
|
1312
|
+
areChildrenAccepted: true,
|
|
1313
|
+
website: "website",
|
|
1314
|
+
logo: undefined,
|
|
1315
|
+
image: null,
|
|
1316
|
+
cares: [],
|
|
1317
|
+
cancellationHoursLimit: undefined
|
|
1318
|
+
},
|
|
1319
|
+
{type: SpaInformation}
|
|
1320
|
+
);
|
|
1321
|
+
|
|
1322
|
+
expect(result).toEqual({
|
|
1323
|
+
are_children_accepted: true,
|
|
1324
|
+
currency: "",
|
|
1325
|
+
description: "",
|
|
1326
|
+
email: "",
|
|
1327
|
+
id: 453,
|
|
1328
|
+
image: "",
|
|
1329
|
+
label: "",
|
|
1330
|
+
location: "",
|
|
1331
|
+
logo: "",
|
|
1332
|
+
phone: "",
|
|
1333
|
+
website: "website",
|
|
1334
|
+
cancellation_hours_limit: null
|
|
1335
|
+
});
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
describe("when jsonMapper.strictGroups = false", () => {
|
|
1339
|
+
it("should serialize props", () => {
|
|
1340
|
+
class Model {
|
|
1341
|
+
@Property()
|
|
1342
|
+
id: string;
|
|
1343
|
+
|
|
1344
|
+
@Groups("summary")
|
|
1345
|
+
ignored: boolean;
|
|
1346
|
+
|
|
1347
|
+
@Name("renamed")
|
|
1348
|
+
name: string;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
const test = new Model();
|
|
1352
|
+
test.id = "id";
|
|
1353
|
+
test.ignored = true;
|
|
1354
|
+
test.name = "myname";
|
|
1355
|
+
|
|
1356
|
+
const result = serialize(test, {type: Model});
|
|
1357
|
+
|
|
1358
|
+
expect(result).toEqual({
|
|
1359
|
+
id: "id",
|
|
1360
|
+
ignored: true,
|
|
1361
|
+
renamed: "myname"
|
|
1362
|
+
});
|
|
1363
|
+
});
|
|
1364
|
+
});
|
|
1365
|
+
describe("when jsonMapper.strictGroups = true", () => {
|
|
1366
|
+
beforeEach(() => {
|
|
1367
|
+
JsonMapperSettings.strictGroups = true;
|
|
1368
|
+
});
|
|
1369
|
+
afterEach(() => {
|
|
1370
|
+
JsonMapperSettings.strictGroups = false;
|
|
1371
|
+
});
|
|
1372
|
+
it("should serialize props", () => {
|
|
1373
|
+
class Model {
|
|
1374
|
+
@Property()
|
|
1375
|
+
id: string;
|
|
1376
|
+
|
|
1377
|
+
@Groups("summary")
|
|
1378
|
+
ignored: boolean;
|
|
1379
|
+
|
|
1380
|
+
@Name("renamed")
|
|
1381
|
+
name: string;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
const test = new Model();
|
|
1385
|
+
test.id = "id";
|
|
1386
|
+
test.ignored = true;
|
|
1387
|
+
test.name = "myname";
|
|
1388
|
+
|
|
1389
|
+
const result = serialize(test, {type: Model});
|
|
1390
|
+
|
|
1391
|
+
expect(result).toEqual({
|
|
1392
|
+
id: "id",
|
|
1393
|
+
renamed: "myname"
|
|
1394
|
+
});
|
|
1395
|
+
});
|
|
1396
|
+
});
|
|
1397
|
+
});
|
|
1398
|
+
describe("custom date mapper", () => {
|
|
1399
|
+
it("should use a custom date mapper", () => {
|
|
1400
|
+
class Test {
|
|
1401
|
+
@Property() myDate: Date;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
class CustomDateMapper {
|
|
1405
|
+
deserialize(data: string | number): Date;
|
|
1406
|
+
deserialize(data: boolean | null | undefined): boolean | null | undefined;
|
|
1407
|
+
deserialize(data: any): any {
|
|
1408
|
+
// don't convert unexpected data. In normal case, Ajv reject unexpected data.
|
|
1409
|
+
// But by default, we have to skip data deserialization and let user to apply
|
|
1410
|
+
// the right mapping
|
|
1411
|
+
if (isBoolean(data) || data === null || data === undefined) {
|
|
1412
|
+
return data;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
return new Date(data);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
serialize(object: Date): any {
|
|
1419
|
+
return new Date(object).toDateString();
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
const obj = new Test();
|
|
1424
|
+
obj.myDate = new Date("2022-10-02");
|
|
1425
|
+
|
|
1426
|
+
const types = new Map<any, any>(getJsonMapperTypes());
|
|
1427
|
+
types.set(Date, new CustomDateMapper());
|
|
1428
|
+
|
|
1429
|
+
const result = serialize(obj, {
|
|
1430
|
+
types
|
|
1431
|
+
});
|
|
1432
|
+
expect(result).toEqual({
|
|
1433
|
+
myDate: "Sun Oct 02 2022"
|
|
1434
|
+
});
|
|
1435
|
+
});
|
|
1436
|
+
});
|
|
1437
|
+
describe("discriminator", () => {
|
|
1438
|
+
it("should serialize items", () => {
|
|
1439
|
+
class Event {
|
|
1440
|
+
@DiscriminatorKey() // declare this property as discriminator key
|
|
1441
|
+
type: string;
|
|
1442
|
+
|
|
1443
|
+
@Property()
|
|
1444
|
+
value: string;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
class SubEvent extends Event {
|
|
1448
|
+
@Property()
|
|
1449
|
+
metaSub: string;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
@DiscriminatorValue("page_view") // or @DiscriminatorValue() value can be inferred by the class name
|
|
1453
|
+
class PageView extends SubEvent {
|
|
1454
|
+
@Required()
|
|
1455
|
+
url: string;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
@DiscriminatorValue("action", "click_action")
|
|
1459
|
+
class Action extends SubEvent {
|
|
1460
|
+
@Required()
|
|
1461
|
+
event: string;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
@DiscriminatorValue()
|
|
1465
|
+
class CustomAction extends Event {
|
|
1466
|
+
@Required()
|
|
1467
|
+
event: string;
|
|
1468
|
+
|
|
1469
|
+
@Property()
|
|
1470
|
+
meta: string;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
type OneOfEvents = PageView | Action | CustomAction;
|
|
1474
|
+
|
|
1475
|
+
const event = new Event();
|
|
1476
|
+
event.value = "value";
|
|
1477
|
+
|
|
1478
|
+
const pageView = new PageView();
|
|
1479
|
+
pageView.value = "value";
|
|
1480
|
+
pageView.url = "url";
|
|
1481
|
+
|
|
1482
|
+
const action = new Action();
|
|
1483
|
+
action.value = "value";
|
|
1484
|
+
action.event = "event";
|
|
1485
|
+
|
|
1486
|
+
const list = [event, pageView, action];
|
|
1487
|
+
|
|
1488
|
+
expect(serialize(list)).toEqual([
|
|
1489
|
+
{
|
|
1490
|
+
value: "value"
|
|
1491
|
+
},
|
|
1492
|
+
{
|
|
1493
|
+
type: "page_view",
|
|
1494
|
+
url: "url",
|
|
1495
|
+
value: "value"
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
type: "action",
|
|
1499
|
+
event: "event",
|
|
1500
|
+
value: "value"
|
|
1501
|
+
}
|
|
1502
|
+
]);
|
|
1503
|
+
});
|
|
1504
|
+
});
|
|
1505
|
+
});
|