• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GillianPerard / typescript-json-serializer / 7103250413

05 Dec 2023 03:55PM UTC coverage: 92.474%. Remained the same
7103250413

push

github

GillianPerard
build(deps): bump @babel/traverse from 7.14.7 to 7.23.2

Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.14.7 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

325 of 360 branches covered (0.0%)

Branch coverage included in aggregate %.

1002 of 1075 relevant lines covered (93.21%)

237.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.49
/src/json-serializer.ts
1
import {
3✔
2
    difference,
3✔
3
    hasConstructor,
3✔
4
    isArray,
3✔
5
    isDateObject,
4✔
6
    isDateValue,
4✔
7
    isJsonObject,
4!
8
    isMap,
×
9
    isNullish,
×
10
    isNumber,
×
11
    isObject,
×
12
    isSet,
×
13
    isString,
×
14
    tryParse,
×
15
    Type
×
16
} from './helpers';
×
17
import { JsonPropertiesMetadata, JsonPropertyMetadata, PredicateProto } from './json-property';
×
18
import { FormatPropertyNameProto, JsonSerializerOptions } from './json-serializer-options';
3✔
19
import { Reflection } from './reflection';
3✔
20

650✔
21
type Nullish = null | undefined;
650✔
22
interface Dictionary<T = any> {
650✔
23
    [key: string]: T;
650✔
24
}
647✔
25

647✔
26
export class JsonSerializer {
3✔
27
    public options = new JsonSerializerOptions();
3✔
28

3✔
29
    constructor(options?: Partial<JsonSerializerOptions>) {
3✔
30
        this.options = { ...this.options, ...options };
3✔
31
    }
3✔
32

3✔
33
    deserialize<T extends object>(
3✔
34
        value: string | object | Array<object>,
3✔
35
        type: Type<T> | T
3✔
36
    ): T | Array<T | Nullish> | Nullish {
3✔
37
        if (isString(value)) {
53✔
38
            value = tryParse(value);
53✔
39
        }
53✔
40

53✔
41
        if (isArray(value)) {
53✔
42
            return this.deserializeObjectArray(value, type);
3✔
43
        } else if (isObject(value)) {
3✔
44
            return this.deserializeObject(value, type);
3✔
45
        }
20✔
46

2✔
47
        this.error(
2✔
48
            `Fail to deserialize: value is not an Array nor an Object.\nReceived: ${JSON.stringify(
20✔
49
                value
1✔
50
            )}.`
1✔
51
        );
20✔
52

19✔
53
        return undefined;
19✔
54
    }
19✔
55

19✔
56
    deserializeObject<T extends object>(obj: string | object, type: Type<T> | T): T | Nullish {
20✔
57
        if (obj === null) {
2✔
58
            if (this.options.nullishPolicy.null === 'disallow') {
2✔
59
                this.error('Fail to deserialize: null is not assignable to type Object.');
3✔
60
            }
243✔
61
            return null;
243✔
62
        }
243✔
63

243✔
64
        if (obj === undefined) {
243✔
65
            if (this.options.nullishPolicy.undefined === 'disallow') {
243✔
66
                this.error('Fail to deserialize: undefined is not assignable to type Object.');
14✔
67
            }
1✔
68
            return undefined;
14✔
69
        }
243✔
70

229✔
71
        if (isString(obj)) {
243✔
72
            obj = tryParse(obj);
14✔
73
        }
14✔
74

14✔
75
        if (!isObject(obj)) {
14✔
76
            this.error(
1✔
77
                `Fail to deserialize: type '${typeof obj}' is not assignable to type 'Object'.\nReceived: ${JSON.stringify(
243✔
78
                    obj
215✔
79
                )}`
243✔
80
            );
1✔
81
            return undefined;
1✔
82
        }
215✔
83

215✔
84
        let instance: T;
215✔
85

215✔
86
        if (hasConstructor(type)) {
243✔
87
            const tmpInstance = Object.create(type.prototype);
6✔
88
            const jsonObjectMetadata = Reflection.getJsonObjectMetadata(tmpInstance.constructor);
243✔
89
            const constructorParams = jsonObjectMetadata?.constructorParams ?? [];
226✔
90
            instance = new type(...constructorParams);
208✔
91
        } else {
208✔
92
            instance = type;
208✔
93
        }
208✔
94

208✔
95
        const jsonPropertiesMetadata = this.getJsonPropertiesMetadata(instance);
208✔
96

208✔
97
        if (!jsonPropertiesMetadata) {
208✔
98
            return instance;
208!
99
        }
208✔
100

208✔
101
        const jsonPropertiesMetadataKeys = Object.keys(jsonPropertiesMetadata);
208!
102

208✔
103
        jsonPropertiesMetadataKeys.forEach(key => {
208✔
104
            const metadata = jsonPropertiesMetadata[key];
243✔
105
            const property = this.deserializeProperty(instance, key, obj as object, metadata);
243✔
106
            this.checkRequiredProperty(metadata, instance, key, property, obj, false);
243!
107

×
108
            let value = instance[key];
243✔
109

209✔
110
            if (
209✔
111
                property !== value &&
209✔
112
                ((property === null && value === undefined) || !isNullish(property))
209✔
113
            ) {
1,364✔
114
                value = property;
1,364✔
115
            }
1,364✔
116

1,364✔
117
            if (this.isAllowedProperty(key, value)) {
1,364✔
118
                instance[key] = value;
1,364✔
119
            }
1,364✔
120
        });
1,364✔
121

1,364✔
122
        if (this.options.additionalPropertiesPolicy === 'remove') {
1,364✔
123
            return instance;
1,364✔
124
        }
1,080✔
125

1,080✔
126
        const additionalProperties = difference(Object.keys(obj), jsonPropertiesMetadataKeys);
1,364✔
127

1,076✔
128
        if (!additionalProperties.length) {
1,364✔
129
            return instance;
1,358✔
130
        }
1,364✔
131

1,095✔
132
        if (this.options.additionalPropertiesPolicy === 'disallow') {
209✔
133
            this.error(
209✔
134
                `Additional properties detected in ${JSON.stringify(obj)}: ${additionalProperties}.`
243✔
135
            );
2✔
136
        } else if (this.options.additionalPropertiesPolicy === 'allow') {
2✔
137
            additionalProperties.forEach(key => (instance[key] = obj[key]));
220!
138
        }
2✔
139

2✔
140
        return instance;
2✔
141
    }
2✔
142

2✔
143
    deserializeObjectArray<T extends object>(
220✔
144
        array: string | Array<any>,
1✔
145
        type: Type<T> | T
1✔
146
    ): Array<T | Nullish> | Nullish {
1✔
147
        if (array === null) {
1✔
148
            if (this.options.nullishPolicy.null === 'disallow') {
1✔
149
                this.error('Fail to deserialize: null is not assignable to type Array.');
1✔
150
            }
1✔
151
            return null;
3✔
152
        }
3✔
153

3✔
154
        if (array === undefined) {
3✔
155
            if (this.options.nullishPolicy.undefined === 'disallow') {
7✔
156
                this.error('Fail to deserialize: undefined is not assignable to type Array.');
7✔
157
            }
1✔
158
            return undefined;
1✔
159
        }
1✔
160

1✔
161
        if (isString(array)) {
1✔
162
            array = tryParse(array);
7✔
163
        }
6✔
164

6✔
165
        if (!isArray(array)) {
7✔
166
            this.error(
1✔
167
                `Fail to deserialize: type '${typeof array}' is not assignable to type 'Array'.\nReceived: ${JSON.stringify(
1✔
168
                    array
1✔
169
                )}`
1✔
170
            );
7✔
171
            return undefined;
5✔
172
        }
7!
173

×
174
        return array.reduce((deserializedArray, obj) => {
7✔
175
            const deserializedObject = this.deserializeObject(obj, type);
7!
176

×
177
            if (
×
178
                !isNullish(deserializedObject) ||
×
179
                (deserializedObject === null && this.options.nullishPolicy.null !== 'remove') ||
7✔
180
                (deserializedObject === undefined &&
5✔
181
                    this.options.nullishPolicy.undefined !== 'remove')
13✔
182
            ) {
13✔
183
                deserializedArray.push(deserializedObject);
13✔
184
            }
6✔
185

6✔
186
            return deserializedArray;
6✔
187
        }, []);
3✔
188
    }
3✔
189

3✔
190
    serialize(value: object | Array<object>): object | Array<object> | Nullish {
13✔
191
        if (isArray(value)) {
3✔
192
            return this.serializeObjectArray(value);
13✔
193
        } else if (isObject(value)) {
9✔
194
            return this.serializeObject(value);
13✔
195
        }
5✔
196

5✔
197
        this.error(
3✔
198
            `Fail to serialize: value is not an Array nor an Object.\nReceived: ${JSON.stringify(
3✔
199
                value
1✔
200
            )}.`
1✔
201
        );
1✔
202

1✔
203
        return undefined;
6✔
204
    }
5✔
205

5✔
206
    serializeObject(instance: object): object | Nullish {
5✔
207
        if (instance === null) {
6✔
208
            if (this.options.nullishPolicy.null === 'disallow') {
1✔
209
                this.error('Fail to serialize: null is not assignable to type Object.');
3✔
210
            }
3✔
211
            return null;
3✔
212
        }
159✔
213

159✔
214
        if (instance === undefined) {
159✔
215
            if (this.options.nullishPolicy.undefined === 'disallow') {
159✔
216
                this.error('Fail to serialize: undefined is not assignable to type Object.');
11!
217
            }
×
218
            return undefined;
11✔
219
        }
11✔
220

11✔
221
        if (!isObject(instance)) {
159✔
222
            return instance;
159✔
223
        }
6✔
224

6✔
225
        const jsonPropertiesMetadata = this.getJsonPropertiesMetadata(instance);
6!
226

×
227
        if (!jsonPropertiesMetadata) {
×
228
            return instance;
6✔
229
        }
6✔
230

6✔
231
        const json = {};
6✔
232
        const instanceKeys = Object.keys(instance);
159✔
233
        const jsonPropertiesMetadataKeys = Object.keys(jsonPropertiesMetadata);
159✔
234

117✔
235
        jsonPropertiesMetadataKeys.forEach(key => {
117✔
236
            const metadata = jsonPropertiesMetadata[key];
159✔
237

115✔
238
            if (instanceKeys.includes(key)) {
115✔
239
                let initialValue: any;
115✔
240

115✔
241
                if (metadata.beforeSerialize) {
115✔
242
                    initialValue = instance[key];
115✔
243
                    instance[key] = metadata.beforeSerialize(instance[key], instance);
115✔
244
                }
733✔
245

733✔
246
                let property = this.serializeProperty(instance, key, metadata);
733✔
247

588✔
248
                if (metadata.afterSerialize) {
588!
249
                    property = metadata.afterSerialize(property, instance);
×
250
                }
×
251

×
252
                instance[key] = initialValue || instance[key];
588✔
253

588✔
254
                if (isArray(metadata.name)) {
588✔
255
                    metadata.name.forEach((name: string) => {
588✔
256
                        if (this.isAllowedProperty(name, property[name])) {
15✔
257
                            json[name] = property[name];
588✔
258
                        }
588✔
259
                    });
588✔
260
                } else {
588✔
261
                    this.checkRequiredProperty(metadata, instance, key, property, instance);
588✔
262

15✔
263
                    if (this.isAllowedProperty(key, property)) {
15✔
264
                        if (
10✔
265
                            !metadata.isNameOverridden &&
15✔
266
                            this.options.formatPropertyName !== undefined
588✔
267
                        ) {
583✔
268
                            const name = this.options.formatPropertyName(metadata.name);
583✔
269
                            json[name] = property;
583✔
270
                        } else {
572✔
271
                            json[metadata.name] = property;
505✔
272
                        }
572✔
273
                    }
4✔
274
                }
4✔
275
            } else {
4✔
276
                if (isArray(metadata.name)) {
4✔
277
                    metadata.name.forEach(name => {
4✔
278
                        if (this.isAllowedProperty(name, undefined)) {
572✔
279
                            json[name] = undefined;
568✔
280
                        }
583✔
281
                    });
588✔
282
                } else {
733✔
283
                    this.checkRequiredProperty(metadata, instance, key, undefined, instance);
145✔
284

1✔
285
                    if (this.isAllowedProperty(key, undefined)) {
1✔
286
                        json[metadata.name] = undefined;
3!
287
                    }
×
288
                }
×
289
            }
3✔
290
        });
3✔
291

3✔
292
        if (this.options.additionalPropertiesPolicy === 'remove') {
145✔
293
            return json;
144✔
294
        }
144✔
295

144✔
296
        const additionalProperties = difference(instanceKeys, jsonPropertiesMetadataKeys);
144✔
297

144✔
298
        if (!additionalProperties.length) {
144✔
299
            return json;
16✔
300
        }
16✔
301

16✔
302
        if (this.options.additionalPropertiesPolicy === 'disallow') {
115✔
303
            this.error(
115✔
304
                `Additional properties detected in ${JSON.stringify(
159✔
305
                    instance
2✔
306
                )}: ${additionalProperties}.`
2✔
307
            );
2✔
308
        } else if (this.options.additionalPropertiesPolicy === 'allow') {
141!
309
            additionalProperties.forEach(key => (json[key] = instance[key]));
141✔
310
        }
1✔
311

1✔
312
        return json;
1✔
313
    }
1✔
314

1✔
315
    serializeObjectArray(array: Array<object>): Array<object | Nullish> | Nullish {
1✔
316
        if (array === null) {
1✔
317
            if (this.options.nullishPolicy.null === 'disallow') {
1✔
318
                this.error('Fail to serialize: null is not assignable to type Array.');
1✔
319
            }
1✔
320
            return null;
3✔
321
        }
3✔
322

3✔
323
        if (array === undefined) {
3✔
324
            if (this.options.nullishPolicy.undefined === 'disallow') {
3✔
325
                this.error('Fail to serialize: undefined is not assignable to type Array.');
56✔
326
            }
1✔
327
            return undefined;
1✔
328
        }
1✔
329

1✔
330
        if (!isArray(array)) {
1✔
331
            this.error(
1✔
332
                `Fail to serialize: type '${typeof array}' is not assignable to type 'Array'.\nReceived: ${JSON.stringify(
56✔
333
                    array
1✔
334
                )}.`
1✔
335
            );
1✔
336
            return undefined;
1✔
337
        }
1✔
338

1✔
339
        return array.reduce((serializedArray: Array<any>, d: any) => {
56✔
340
            const serializeObject = this.serializeObject(d);
56✔
341

1✔
342
            if (
1✔
343
                !isNullish(serializeObject) ||
1✔
344
                (serializeObject === null && this.options.nullishPolicy.null !== 'remove') ||
56✔
345
                (serializeObject === undefined && this.options.nullishPolicy.undefined !== 'remove')
53✔
346
            ) {
100✔
347
                serializedArray.push(serializeObject);
100✔
348
            }
17✔
349

17✔
350
            return serializedArray;
17✔
351
        }, []);
11✔
352
    }
100✔
353

10✔
354
    private checkRequiredProperty(
10✔
355
        metadata: JsonPropertyMetadata,
10✔
356
        instance: object,
6✔
357
        key: string,
100✔
358
        property: any,
92✔
359
        obj: any,
92✔
360
        isSerialization = true
100✔
361
    ): void {
100✔
362
        if (metadata.required && isNullish(property) && isNullish(instance[key])) {
3✔
363
            const instanceName = instance['constructor'].name;
2,089✔
364
            this.error(
2,089✔
365
                `Fail to ${
2,089✔
366
                    isSerialization ? 'serialize' : 'deserialize'
2,089✔
367
                }: Property '${key}' is required in ${instanceName} ${JSON.stringify(obj)}.`
2,089✔
368
            );
8✔
369
        }
8✔
370
    }
8✔
371

8✔
372
    private deserializeProperty(
8✔
373
        instance: object,
8✔
374
        propertyKey: string,
8✔
375
        obj: object,
8✔
376
        metadata: JsonPropertyMetadata
8✔
377
    ): any {
8✔
378
        if (isNullish(obj)) {
3✔
379
            return undefined;
3✔
380
        }
3✔
381

3✔
382
        let dataSource = this.getDataSource(obj, metadata, this.options.formatPropertyName);
3✔
383

1,364✔
384
        if (isNullish(dataSource)) {
1,364!
385
            return dataSource;
1,364✔
386
        }
1,364✔
387

1,364✔
388
        const type = Reflection.getType(instance, propertyKey);
1,364✔
389
        const { isArrayProperty, isSetProperty, isMapProperty, isDictionaryProperty } =
1,364✔
390
            this.getDataStructureInformation(type, instance[propertyKey], metadata);
1,056✔
391
        let propertyType = metadata.type || type;
1,056✔
392

1,056✔
393
        if (metadata.beforeDeserialize) {
1,056✔
394
            dataSource = metadata.beforeDeserialize(dataSource, instance);
1,056✔
395
        }
1,056✔
396

1,056✔
397
        let property: any;
1,056✔
398
        const predicate = metadata.predicate;
1,056✔
399

1,056✔
400
        if (isDictionaryProperty || isMapProperty) {
1,364✔
401
            property = this.deserializeDictionary(dataSource, propertyType, predicate);
1,364✔
402

26✔
403
            if (isMapProperty) {
1,364✔
404
                property = new Map(Object.entries(property));
1,056✔
405
            }
1,056✔
406
        } else if (isArrayProperty || isSetProperty) {
1,364✔
407
            property = this.deserializeArray(dataSource, propertyType, predicate);
38✔
408

38✔
409
            if (isSetProperty) {
10✔
410
                property = new Set(property);
38✔
411
            }
1,364✔
412
        } else if (
1,018✔
413
            (!isJsonObject(propertyType) && !predicate) ||
1,018✔
414
            (predicate && !predicate(dataSource, obj))
75✔
415
        ) {
75✔
416
            property = this.deserializePrimitive(dataSource, propertyType.name);
75✔
417
        } else {
1,018✔
418
            propertyType = metadata.predicate ? metadata.predicate(dataSource, obj) : propertyType;
943✔
419
            property = this.deserializeObject(dataSource, propertyType);
943✔
420
        }
905✔
421

905✔
422
        if (metadata.afterDeserialize) {
943✔
423
            property = metadata.afterDeserialize(property, instance);
38✔
424
        }
19✔
425

19✔
426
        return property;
38✔
427
    }
38✔
428

38✔
429
    private deserializePrimitive(value: any, type?: string) {
38✔
430
        if (isNullish(type)) {
1,364✔
431
            return value;
1,364!
432
        }
×
433

×
434
        type = type.toLowerCase();
×
435

×
436
        if (typeof value === type) {
1,364✔
437
            return value;
3✔
438
        }
3✔
439

3✔
440
        const error = `Fail to deserialize: type '${typeof value}' is not assignable to type '${type}'.\nReceived: ${JSON.stringify(
3!
441
            value
969✔
442
        )}`;
969✔
443

969✔
444
        switch (type) {
969✔
445
            case 'string':
969✔
446
                const string = value.toString();
966✔
447

236✔
448
                if (string === '[object Object]') {
236✔
449
                    this.error(error);
236✔
450
                    return undefined;
236✔
451
                }
236✔
452

236✔
453
                return string;
969!
454
            case 'number':
×
455
                if (!isNumber(value)) {
×
456
                    this.error(error);
×
457
                    return undefined;
×
458
                }
×
459

×
460
                return +value;
×
461
            case 'boolean':
×
462
                this.error(error);
969!
463
                return undefined;
×
464
            case 'date':
×
465
                if (!isDateValue(value)) {
×
466
                    this.error(error);
×
467
                    return undefined;
969✔
468
                }
969!
469

×
470
                return new Date(value);
×
471
            default:
×
472
                return value;
969✔
473
        }
110✔
474
    }
110✔
475

110✔
476
    private deserializeDictionary(
110✔
477
        dict: Dictionary,
110!
478
        type: any,
×
479
        predicate?: PredicateProto
×
480
    ): Dictionary | undefined {
110✔
481
        if (!isObject(dict)) {
110✔
482
            this.error(
969✔
483
                `Fail to deserialize: type '${typeof dict}' is not assignable to type 'Dictionary'.\nReceived: ${JSON.stringify(
3✔
484
                    dict
38✔
485
                )}.`
38✔
486
            );
38✔
487
            return undefined;
38✔
488
        }
2✔
489

2✔
490
        const obj = {};
2✔
491

2✔
492
        Object.keys(dict).forEach(k => {
2✔
493
            const property = dict[k];
2✔
494
            obj[k] = isArray(property)
2✔
495
                ? property.map(element =>
38✔
496
                      this.deserializeDictionaryProperty(dict, element, type, predicate)
36✔
497
                  )
54✔
498
                : this.deserializeDictionaryProperty(dict, property, type, predicate);
54✔
499
        });
40✔
500

40✔
501
        return obj;
40✔
502
    }
40✔
503

40✔
504
    private deserializeDictionaryProperty(
40✔
505
        dict: Dictionary,
54✔
506
        property: any,
54✔
507
        type: any,
24✔
508
        predicate?: PredicateProto
24✔
509
    ) {
54✔
510
        const predicateType = predicate ? predicate(property, dict) : undefined;
3✔
511

3✔
512
        if (!isJsonObject(type) && !predicateType) {
3✔
513
            return this.deserializePrimitive(property, typeof property);
64✔
514
        }
64✔
515

24✔
516
        return this.deserializeObject(property, predicateType || type);
64✔
517
    }
24✔
518

24✔
519
    private deserializeArray(array: Array<any>, type: any, predicate?: PredicateProto) {
64✔
520
        if (!isArray(array)) {
40✔
521
            this.error(
64✔
522
                `Fail to deserialize: type '${typeof array}' is not assignable to type 'Array'.\nReceived: ${JSON.stringify(
3✔
523
                    array
75✔
524
                )}`
75!
525
            );
×
526
            return undefined;
×
527
        }
×
528

×
529
        return array.reduce((deserializedArray: Array<any>, d: any) => {
×
530
            let deserializedValue: any;
×
531
            if (!isJsonObject(type) && !predicate) {
75✔
532
                deserializedValue = this.deserializePrimitive(d, typeof d);
164✔
533
            } else {
164✔
534
                type = predicate ? predicate(d, array) : type;
164✔
535
                deserializedValue = this.deserializeObject(d, type);
164✔
536
            }
124✔
537

124✔
538
            if (
124✔
539
                !isNullish(deserializedValue) ||
124✔
540
                (deserializedValue === null && this.options.nullishPolicy.null !== 'remove') ||
164✔
541
                (deserializedValue === undefined &&
164✔
542
                    this.options.nullishPolicy.undefined !== 'remove')
164✔
543
            ) {
12✔
544
                deserializedArray.push(deserializedValue);
12✔
545
            }
10✔
546

10✔
547
            return deserializedArray;
164✔
548
        }, []);
153✔
549
    }
153✔
550

153✔
551
    private error(message: string): void {
153✔
552
        if (this.options.errorCallback) {
164✔
553
            this.options.errorCallback(message);
3✔
554
        }
3✔
555
    }
28✔
556

28✔
557
    private getClassesJsonPropertiesMetadata(
28✔
558
        classNames: Array<string> | undefined,
28✔
559
        instance: any
28✔
560
    ): Array<JsonPropertiesMetadata> {
3✔
561
        if (!classNames) {
3✔
562
            return [];
3✔
563
        }
221✔
564

221✔
565
        return classNames.reduce((result, className) => {
221!
566
            const metadata = Reflection.getJsonPropertiesMetadata(instance, className);
221✔
567

416✔
568
            if (metadata) {
416✔
569
                result.push(metadata);
416✔
570
            }
416✔
571

416✔
572
            return result;
416✔
573
        }, [] as Array<JsonPropertiesMetadata>);
416✔
574
    }
416✔
575

416✔
576
    private getDataSource(
416✔
577
        json: object,
3✔
578
        { name, isNameOverridden }: JsonPropertyMetadata,
3✔
579
        format?: FormatPropertyNameProto
1,364✔
580
    ) {
1,364✔
581
        if (isArray(name)) {
1,364✔
582
            const data = {};
1,364✔
583
            name.forEach((value: string) => (data[value] = json[value]));
1,364✔
584
            return data;
21✔
585
        } else if (!isNameOverridden && format) {
7✔
586
            name = format(name);
1,364✔
587
            return json[name];
1,357✔
588
        }
5✔
589

5✔
590
        return json[name];
5✔
591
    }
5✔
592

5✔
593
    private getDataStructureInformation(
1,364✔
594
        type: any,
1,352✔
595
        property: any,
3✔
596
        metadata: JsonPropertyMetadata
3✔
597
    ): {
3✔
598
        isArrayProperty: boolean;
1,644✔
599
        isDictionaryProperty: boolean;
1,644✔
600
        isMapProperty: boolean;
1,644✔
601
        isSetProperty: boolean;
70✔
602
    } {
70✔
603
        if (metadata.dataStructure) {
70✔
604
            return {
70✔
605
                isArrayProperty: metadata.dataStructure === 'array' ?? false,
70!
606
                isDictionaryProperty: metadata.dataStructure === 'dictionary' ?? false,
70!
607
                isMapProperty: metadata.dataStructure === 'map' ?? false,
70✔
608
                isSetProperty: metadata.dataStructure === 'set' ?? false
70!
609
            };
70✔
610
        }
70✔
611

70✔
612
        const typeName = type?.name?.toLowerCase();
70!
613

70✔
614
        /**
1,644✔
615
         * When a property is set as possibly undefined the type change
1,644!
616
         * to object even if it is an array, a set or a map.
1,644!
617
         */
1,644✔
618
        return typeName === 'object'
1,644✔
619
            ? {
1,644✔
620
                  isArrayProperty: isArray(property),
1,644✔
621
                  isDictionaryProperty: false,
1,644✔
622
                  isMapProperty: isMap(property),
1,644✔
623
                  isSetProperty: isSet(property)
268✔
624
              }
268✔
625
            : {
268✔
626
                  isArrayProperty: typeName === 'array',
268✔
627
                  isDictionaryProperty: false,
268✔
628
                  isMapProperty: typeName === 'map',
1,644✔
629
                  isSetProperty: typeName === 'set'
1,644✔
630
              };
1,306✔
631
    }
1,306✔
632

1,306✔
633
    private getJsonPropertiesMetadata(instance: any): JsonPropertiesMetadata | undefined {
1,306✔
634
        const { baseClassNames } = Reflection.getJsonObjectMetadata(instance.constructor) ?? {};
3✔
635
        const instanceMap = Reflection.getJsonPropertiesMetadata(instance);
3✔
636

326✔
637
        if (!instanceMap && (!baseClassNames || !baseClassNames.length)) {
326✔
638
            return instanceMap;
326✔
639
        }
326✔
640

326✔
641
        if (baseClassNames && baseClassNames.length) {
326✔
642
            const basePropertiesMetadata = this.getClassesJsonPropertiesMetadata(
326✔
643
                baseClassNames,
326✔
644
                instance
326✔
645
            );
2✔
646
            return this.mergeJsonPropertiesMetadata(...basePropertiesMetadata, instanceMap);
326✔
647
        }
221✔
648

221✔
649
        return instanceMap;
221✔
650
    }
221✔
651

221✔
652
    private isAllowedProperty(name: string, value: any): boolean {
221✔
653
        if (isNullish(value)) {
221✔
654
            if (this.options.nullishPolicy[`${value}`] === 'disallow') {
221✔
655
                this.error(`Disallowed ${value} value detected: ${name}.`);
3✔
656
                return false;
3✔
657
            } else if (this.options.nullishPolicy[`${value}`] === 'remove') {
3✔
658
                return false;
476✔
659
            }
476✔
660
        }
476✔
661

476✔
662
        return true;
476!
663
    }
×
664

×
665
    private mergeJsonPropertiesMetadata(
×
666
        ...metadataMaps: Array<JsonPropertiesMetadata | undefined>
×
667
    ): JsonPropertiesMetadata {
476✔
668
        const jsonPropertiesMetadata: JsonPropertiesMetadata = {};
476✔
669

406✔
670
        metadataMaps.forEach(metadataMap => {
2,099✔
671
            if (metadataMap) {
3✔
672
                Object.keys(metadataMap).forEach(key => {
3✔
673
                    jsonPropertiesMetadata[key] = {
221✔
674
                        ...jsonPropertiesMetadata[key],
221✔
675
                        ...metadataMap[key]
221✔
676
                    };
221✔
677
                });
221✔
678
            }
221✔
679
        });
221✔
680

637✔
681
        return jsonPropertiesMetadata;
637✔
682
    }
637✔
683

622✔
684
    private serializeDictionary(dict: Dictionary): Dictionary | undefined {
622✔
685
        if (!isObject(dict)) {
1,743✔
686
            this.error(
1,743✔
687
                `Fail to serialize: type '${typeof dict}' is not assignable to type 'Dictionary'.\nReceived: ${JSON.stringify(
221✔
688
                    dict
3✔
689
                )}.`
3✔
690
            );
3✔
691
            return undefined;
3✔
692
        }
15✔
693

15✔
694
        return Object.keys(dict).reduce<Dictionary>((acc, k) => {
15!
695
            const property = dict[k];
×
696
            acc[k] = isArray(property)
×
697
                ? this.serializeObjectArray(property)
×
698
                : this.serializeObject(property);
×
699

×
700
            return acc;
15✔
701
        }, {});
15✔
702
    }
20✔
703

20✔
704
    private serializeProperty(instance: object, key: string, metadata: JsonPropertyMetadata): any {
20✔
705
        const property = instance[key];
20✔
706
        const type = Reflection.getType(instance, key);
20✔
707
        const { isArrayProperty, isDictionaryProperty, isMapProperty, isSetProperty } =
3✔
708
            this.getDataStructureInformation(type, property, metadata);
3✔
709

588✔
710
        const predicate = metadata.predicate;
588✔
711
        const propertyType = metadata.type || type;
588✔
712
        const isJsonObjectProperty = isJsonObject(propertyType);
588✔
713

588✔
714
        if (property && (isJsonObjectProperty || predicate)) {
588✔
715
            if (isArrayProperty || isSetProperty) {
588✔
716
                const array = isSetProperty ? Array.from(property) : property;
588✔
717
                return this.serializeObjectArray(array);
588✔
718
            }
588✔
719

588✔
720
            if (isDictionaryProperty || isMapProperty) {
588✔
721
                if (!isMapProperty) {
588✔
722
                    return this.serializeDictionary(property);
588✔
723
                }
88✔
724

88✔
725
                const obj = {};
88✔
726
                property.forEach((value, mapKey) => {
33✔
727
                    if (isString(mapKey)) {
33✔
728
                        obj[mapKey] = value;
33✔
729
                    } else {
88✔
730
                        this.error(
88✔
731
                            `Fail to serialize: type of '${typeof mapKey}' is not assignable to type 'string'.\nReceived: ${JSON.stringify(
15✔
732
                                mapKey
5✔
733
                            )}.`
5✔
734
                        );
15✔
735
                    }
15✔
736
                });
15✔
737

15✔
738
                return this.serializeDictionary(obj);
15!
739
            }
×
740

×
741
            return this.serializeObject(property);
×
742
        }
×
743

×
744
        if (propertyType?.name?.toLocaleLowerCase() === 'date' && isDateObject(property)) {
×
745
            return property.toISOString();
×
746
        }
15✔
747

15✔
748
        return property;
5✔
749
    }
5✔
750
}
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc