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