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

GillianPerard / typescript-json-serializer / 3860418162

pending completion
3860418162

push

github

GitHub
build(deps): bump json5 from 2.2.0 to 2.2.3

265 of 312 branches covered (84.94%)

Branch coverage included in aggregate %.

934 of 1034 relevant lines covered (90.33%)

228.98 hits per line

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

86.72
/src/json-serializer.ts
1
import {
1✔
2
    difference,
1✔
3
    hasConstructor,
1✔
4
    isArray,
1✔
5
    isDateObject,
2✔
6
    isDateValue,
2✔
7
    isJsonObject,
2!
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';
1✔
19
import { Reflection } from './reflection';
1✔
20

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

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

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

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

45✔
41
        if (isArray(value)) {
45✔
42
            return this.deserializeObjectArray(value, type);
1✔
43
        } else if (isObject(value)) {
1✔
44
            return this.deserializeObject(value, type);
1✔
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.');
1✔
60
            }
210✔
61
            return null;
210✔
62
        }
210✔
63

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

202✔
71
        if (isString(obj)) {
210✔
72
            obj = tryParse(obj);
8✔
73
        }
8✔
74

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

194✔
84
        const instance: T = hasConstructor(type) ? new type({}) : type;
210✔
85
        const jsonPropertiesMetadata = this.getJsonPropertiesMetadata(instance);
1✔
86

1✔
87
        if (!jsonPropertiesMetadata) {
1✔
88
            return instance;
210✔
89
        }
193✔
90

193✔
91
        const jsonPropertiesMetadataKeys = Object.keys(jsonPropertiesMetadata);
210✔
92

210✔
93
        jsonPropertiesMetadataKeys.forEach(key => {
210✔
94
            const metadata = jsonPropertiesMetadata[key];
210!
95
            const property = this.deserializeProperty(instance, key, obj as object, metadata);
210✔
96
            this.checkRequiredProperty(metadata, instance, key, property, obj, false);
193✔
97

1,281✔
98
            if (this.isAllowedProperty(key, property)) {
1,281✔
99
                instance[key] = property;
1,281✔
100
            }
1,281✔
101
        });
1,281✔
102

1,281✔
103
        if (this.options.additionalPropertiesPolicy === 'remove') {
1,281✔
104
            return instance;
1,281✔
105
        }
1,281✔
106

1,281✔
107
        const additionalProperties = difference(Object.keys(obj), jsonPropertiesMetadataKeys);
193✔
108

193✔
109
        if (!additionalProperties.length) {
193✔
110
            return instance;
210✔
111
        }
185✔
112

185✔
113
        if (this.options.additionalPropertiesPolicy === 'disallow') {
210✔
114
            this.error(
2✔
115
                `Additional properties detected in ${JSON.stringify(obj)}: ${additionalProperties}.`
210!
116
            );
2✔
117
        } else if (this.options.additionalPropertiesPolicy === 'allow') {
210✔
118
            additionalProperties.forEach(key => (instance[key] = obj[key]));
1✔
119
        }
1✔
120

1✔
121
        return instance;
1✔
122
    }
1✔
123

1✔
124
    deserializeObjectArray<T extends object>(
1✔
125
        array: string | Array<any>,
1✔
126
        type: Type<T> | T
1✔
127
    ): Array<T | Nullish> | Nullish {
2✔
128
        if (array === null) {
1✔
129
            if (this.options.nullishPolicy.null === 'disallow') {
1✔
130
                this.error('Fail to deserialize: null is not assignable to type Array.');
1✔
131
            }
4✔
132
            return null;
1✔
133
        }
1✔
134

1✔
135
        if (array === undefined) {
1✔
136
            if (this.options.nullishPolicy.undefined === 'disallow') {
1✔
137
                this.error('Fail to deserialize: undefined is not assignable to type Array.');
4✔
138
            }
1✔
139
            return undefined;
1✔
140
        }
1✔
141

1✔
142
        if (isString(array)) {
1✔
143
            array = tryParse(array);
1✔
144
        }
1✔
145

1✔
146
        if (!isArray(array)) {
1✔
147
            this.error(
1✔
148
                `Fail to deserialize: type '${typeof array}' is not assignable to type 'Array'.\nReceived: ${JSON.stringify(
4!
149
                    array
2✔
150
                )}`
4!
151
            );
×
152
            return undefined;
×
153
        }
×
154

×
155
        return array.reduce((deserializedArray, obj) => {
×
156
            const deserializedObject = this.deserializeObject(obj, type);
4✔
157

2✔
158
            if (
2✔
159
                !isNullish(deserializedObject) ||
2✔
160
                (deserializedObject === null && this.options.nullishPolicy.null !== 'remove') ||
4✔
161
                (deserializedObject === undefined &&
4!
162
                    this.options.nullishPolicy.undefined !== 'remove')
4!
163
            ) {
×
164
                deserializedArray.push(deserializedObject);
×
165
            }
×
166

×
167
            return deserializedArray;
4✔
168
        }, []);
4✔
169
    }
4✔
170

4✔
171
    serialize(value: object | Array<object>): object | Array<object> | Nullish {
4✔
172
        if (isArray(value)) {
1✔
173
            return this.serializeObjectArray(value);
1✔
174
        } else if (isObject(value)) {
6✔
175
            return this.serializeObject(value);
6✔
176
        }
1✔
177

1✔
178
        this.error(
6✔
179
            `Fail to serialize: value is not an Array nor an Object.\nReceived: ${JSON.stringify(
6✔
180
                value
1✔
181
            )}.`
1✔
182
        );
1✔
183

1✔
184
        return undefined;
1✔
185
    }
1✔
186

1✔
187
    serializeObject(instance: object): object | Nullish {
1✔
188
        if (instance === null) {
1✔
189
            if (this.options.nullishPolicy.null === 'disallow') {
1✔
190
                this.error('Fail to serialize: null is not assignable to type Object.');
136✔
191
            }
5✔
192
            return null;
×
193
        }
×
194

×
195
        if (instance === undefined) {
×
196
            if (this.options.nullishPolicy.undefined === 'disallow') {
136✔
197
                this.error('Fail to serialize: undefined is not assignable to type Object.');
136!
198
            }
×
199
            return undefined;
×
200
        }
×
201

×
202
        if (!isObject(instance)) {
×
203
            return instance;
×
204
        }
×
205

×
206
        const jsonPropertiesMetadata = this.getJsonPropertiesMetadata(instance);
136✔
207

25✔
208
        if (!jsonPropertiesMetadata) {
136✔
209
            return instance;
106✔
210
        }
106✔
211

106✔
212
        const json = {};
106✔
213
        const instanceKeys = Object.keys(instance);
136✔
214
        const jsonPropertiesMetadataKeys = Object.keys(jsonPropertiesMetadata);
136✔
215

104✔
216
        jsonPropertiesMetadataKeys.forEach(key => {
104✔
217
            const metadata = jsonPropertiesMetadata[key];
104✔
218

104✔
219
            if (instanceKeys.includes(key)) {
104✔
220
                let initialValue: any;
660✔
221

660✔
222
                if (metadata.beforeSerialize) {
660✔
223
                    initialValue = instance[key];
660✔
224
                    instance[key] = metadata.beforeSerialize(instance[key], instance);
567!
225
                }
×
226

×
227
                let property = this.serializeProperty(instance, key, metadata);
×
228

×
229
                if (metadata.afterSerialize) {
567✔
230
                    property = metadata.afterSerialize(property, instance);
567✔
231
                }
567✔
232

567✔
233
                instance[key] = initialValue || instance[key];
567✔
234

15✔
235
                if (isArray(metadata.name)) {
15✔
236
                    metadata.name.forEach((name: string) => {
567✔
237
                        if (this.isAllowedProperty(name, property[name])) {
567✔
238
                            json[name] = property[name];
5✔
239
                        }
15✔
240
                    });
15✔
241
                } else {
15✔
242
                    this.checkRequiredProperty(metadata, instance, key, property, instance);
15✔
243

15✔
244
                    if (this.isAllowedProperty(key, property)) {
567✔
245
                        if (
562✔
246
                            !metadata.isNameOverridden &&
562✔
247
                            this.options.formatPropertyName !== undefined
562✔
248
                        ) {
560✔
249
                            const name = this.options.formatPropertyName(metadata.name);
560✔
250
                            json[name] = property;
560✔
251
                        } else {
4✔
252
                            json[metadata.name] = property;
4✔
253
                        }
4✔
254
                    }
560✔
255
                }
556✔
256
            } else {
556✔
257
                if (isArray(metadata.name)) {
556✔
258
                    metadata.name.forEach(name => {
562✔
259
                        if (this.isAllowedProperty(name, undefined)) {
660✔
260
                            json[name] = undefined;
93✔
261
                        }
1✔
262
                    });
3✔
263
                } else {
3✔
264
                    this.checkRequiredProperty(metadata, instance, key, undefined, instance);
3!
265

×
266
                    if (this.isAllowedProperty(key, undefined)) {
93✔
267
                        json[metadata.name] = undefined;
92✔
268
                    }
92✔
269
                }
92✔
270
            }
92✔
271
        });
92✔
272

92✔
273
        if (this.options.additionalPropertiesPolicy === 'remove') {
92!
274
            return json;
×
275
        }
×
276

×
277
        const additionalProperties = difference(instanceKeys, jsonPropertiesMetadataKeys);
104✔
278

104✔
279
        if (!additionalProperties.length) {
136✔
280
            return json;
98✔
281
        }
136✔
282

2✔
283
        if (this.options.additionalPropertiesPolicy === 'disallow') {
2✔
284
            this.error(
2✔
285
                `Additional properties detected in ${JSON.stringify(
136!
286
                    instance
136✔
287
                )}: ${additionalProperties}.`
2✔
288
            );
136✔
289
        } else if (this.options.additionalPropertiesPolicy === 'allow') {
1✔
290
            additionalProperties.forEach(key => (json[key] = instance[key]));
1✔
291
        }
1✔
292

1✔
293
        return json;
1✔
294
    }
1✔
295

1✔
296
    serializeObjectArray(array: Array<object>): Array<object | Nullish> | Nullish {
1✔
297
        if (array === null) {
1✔
298
            if (this.options.nullishPolicy.null === 'disallow') {
1✔
299
                this.error('Fail to serialize: null is not assignable to type Array.');
1✔
300
            }
35✔
301
            return null;
1✔
302
        }
1✔
303

1✔
304
        if (array === undefined) {
1✔
305
            if (this.options.nullishPolicy.undefined === 'disallow') {
1✔
306
                this.error('Fail to serialize: undefined is not assignable to type Array.');
35✔
307
            }
1✔
308
            return undefined;
1✔
309
        }
1✔
310

1✔
311
        if (!isArray(array)) {
1✔
312
            this.error(
1✔
313
                `Fail to serialize: type '${typeof array}' is not assignable to type 'Array'.\nReceived: ${JSON.stringify(
35✔
314
                    array
35✔
315
                )}.`
1✔
316
            );
1✔
317
            return undefined;
1✔
318
        }
1✔
319

1✔
320
        return array.reduce((serializedArray: Array<any>, d: any) => {
1✔
321
            const serializeObject = this.serializeObject(d);
35✔
322

32✔
323
            if (
32✔
324
                !isNullish(serializeObject) ||
62✔
325
                (serializeObject === null && this.options.nullishPolicy.null !== 'remove') ||
62✔
326
                (serializeObject === undefined && this.options.nullishPolicy.undefined !== 'remove')
62!
327
            ) {
×
328
                serializedArray.push(serializeObject);
×
329
            }
×
330

×
331
            return serializedArray;
62✔
332
        }, []);
62✔
333
    }
62✔
334

62✔
335
    private checkRequiredProperty(
62✔
336
        metadata: JsonPropertyMetadata,
1✔
337
        instance: object,
1✔
338
        key: string,
1✔
339
        property: any,
1✔
340
        obj: any,
1,933✔
341
        isSerialization = true
1,933✔
342
    ): void {
1,933✔
343
        if (metadata.required && isNullish(property)) {
1,933✔
344
            const instanceName = instance['constructor'].name;
1,933✔
345
            this.error(
1,933✔
346
                `Fail to ${
8✔
347
                    isSerialization ? 'serialize' : 'deserialize'
8✔
348
                }: Property '${key}' is required in ${instanceName} ${JSON.stringify(obj)}.`
8✔
349
            );
8✔
350
        }
8✔
351
    }
8✔
352

8✔
353
    private deserializeProperty(
1✔
354
        instance: object,
1✔
355
        propertyKey: string,
1✔
356
        obj: object,
1,281✔
357
        metadata: JsonPropertyMetadata
1,281✔
358
    ): any {
1,281!
359
        if (isNullish(obj)) {
×
360
            return undefined;
1,281✔
361
        }
1,281✔
362

1,281✔
363
        let dataSource = this.getDataSource(obj, metadata, this.options.formatPropertyName);
1,281✔
364

1,281✔
365
        if (isNullish(dataSource)) {
1,281✔
366
            return dataSource;
1,281✔
367
        }
1,051✔
368

1,051✔
369
        const type = Reflection.getType(instance, propertyKey);
1,051✔
370
        const { isArrayProperty, isSetProperty, isMapProperty, isDictionaryProperty } =
1,051✔
371
            this.getDataStructureInformation(type, instance[propertyKey], metadata);
1,051✔
372
        let propertyType = metadata.type || type;
1,051✔
373

1,051✔
374
        if (metadata.beforeDeserialize) {
1,051✔
375
            dataSource = metadata.beforeDeserialize(dataSource, instance);
1,281✔
376
        }
26✔
377

26✔
378
        let property: any;
26✔
379
        const predicate = metadata.predicate;
1,281✔
380

1,051✔
381
        if (isDictionaryProperty || isMapProperty) {
1,051✔
382
            property = this.deserializeDictionary(dataSource, propertyType, predicate);
1,281✔
383

38✔
384
            if (isMapProperty) {
38✔
385
                property = new Map(Object.entries(property));
38✔
386
            }
19✔
387
        } else if (isArrayProperty || isSetProperty) {
38✔
388
            property = this.deserializeArray(dataSource, propertyType, predicate);
1,013✔
389

72✔
390
            if (isSetProperty) {
72✔
391
                property = new Set(property);
72✔
392
            }
72✔
393
        } else if (
19✔
394
            (!isJsonObject(propertyType) && !predicate) ||
1,013✔
395
            (predicate && !predicate(dataSource, obj))
941✔
396
        ) {
78✔
397
            property = this.deserializePrimitive(dataSource, propertyType.name);
941✔
398
        } else {
903✔
399
            propertyType = metadata.predicate ? metadata.predicate(dataSource, obj) : propertyType;
941✔
400
            property = this.deserializeObject(dataSource, propertyType);
38✔
401
        }
38✔
402

38✔
403
        if (metadata.afterDeserialize) {
38✔
404
            property = metadata.afterDeserialize(property, instance);
1,281!
405
        }
×
406

×
407
        return property;
×
408
    }
×
409

×
410
    private deserializePrimitive(value: any, type?: string) {
1✔
411
        if (isNullish(type)) {
1✔
412
            return value;
1✔
413
        }
967✔
414

967✔
415
        type = type.toLowerCase();
967✔
416

967✔
417
        if (typeof value === type) {
967!
418
            return value;
967✔
419
        }
967✔
420

967✔
421
        const error = `Fail to deserialize: type '${typeof value}' is not assignable to type '${type}'.\nReceived: ${JSON.stringify(
967✔
422
            value
179✔
423
        )}`;
179✔
424

179✔
425
        switch (type) {
179✔
426
            case 'string':
179✔
427
                const string = value.toString();
967!
428

×
429
                if (string === '[object Object]') {
×
430
                    this.error(error);
×
431
                    return undefined;
×
432
                }
×
433

×
434
                return string;
×
435
            case 'number':
×
436
                if (!isNumber(value)) {
967✔
437
                    this.error(error);
967!
438
                    return undefined;
×
439
                }
×
440

×
441
                return +value;
×
442
            case 'boolean':
×
443
                this.error(error);
×
444
                return undefined;
967!
445
            case 'date':
×
446
                if (!isDateValue(value)) {
967✔
447
                    this.error(error);
967✔
448
                    return undefined;
110!
449
                }
×
450

×
451
                return new Date(value);
×
452
            default:
×
453
                return value;
110✔
454
        }
110✔
455
    }
110✔
456

110✔
457
    private deserializeDictionary(
967✔
458
        dict: Dictionary,
967✔
459
        type: any,
1✔
460
        predicate?: PredicateProto
1✔
461
    ): Dictionary | undefined {
1✔
462
        if (!isObject(dict)) {
38✔
463
            this.error(
38✔
464
                `Fail to deserialize: type '${typeof dict}' is not assignable to type 'Dictionary'.\nReceived: ${JSON.stringify(
38✔
465
                    dict
2✔
466
                )}.`
2✔
467
            );
2✔
468
            return undefined;
2✔
469
        }
38✔
470

36✔
471
        const obj = {};
36✔
472

36✔
473
        Object.keys(dict).forEach(k => {
36✔
474
            const predicateType = predicate ? predicate(dict[k], dict) : undefined;
54✔
475

54✔
476
            if (!isJsonObject(type) && !predicateType) {
54✔
477
                obj[k] = this.deserializePrimitive(dict[k], typeof dict[k]);
54✔
478
            } else {
24✔
479
                obj[k] = this.deserializeObject(dict[k], predicateType || type);
30✔
480
            }
30✔
481
        });
30✔
482

30✔
483
        return obj;
36✔
484
    }
36✔
485

36✔
486
    private deserializeArray(array: Array<any>, type: any, predicate?: PredicateProto) {
1✔
487
        if (!isArray(array)) {
72✔
488
            this.error(
72✔
489
                `Fail to deserialize: type '${typeof array}' is not assignable to type 'Array'.\nReceived: ${JSON.stringify(
72!
490
                    array
×
491
                )}`
×
492
            );
×
493
            return undefined;
72✔
494
        }
72✔
495

72✔
496
        return array.reduce((deserializedArray: Array<any>, d: any) => {
72✔
497
            let deserializedValue: any;
155✔
498
            if (!isJsonObject(type) && !predicate) {
155✔
499
                deserializedValue = this.deserializePrimitive(d, typeof d);
155✔
500
            } else {
115✔
501
                type = predicate ? predicate(d, array) : type;
115✔
502
                deserializedValue = this.deserializeObject(d, type);
115✔
503
            }
155✔
504

155✔
505
            if (
155✔
506
                !isNullish(deserializedValue) ||
155✔
507
                (deserializedValue === null && this.options.nullishPolicy.null !== 'remove') ||
155✔
508
                (deserializedValue === undefined &&
7✔
509
                    this.options.nullishPolicy.undefined !== 'remove')
155✔
510
            ) {
148✔
511
                deserializedArray.push(deserializedValue);
155✔
512
            }
155✔
513

155✔
514
            return deserializedArray;
1✔
515
        }, []);
1✔
516
    }
1✔
517

1✔
518
    private error(message: string): void {
1✔
519
        if (this.options.errorCallback) {
23✔
520
            this.options.errorCallback(message);
23✔
521
        }
1✔
522
    }
1✔
523

1✔
524
    private getClassesJsonPropertiesMetadata(
1✔
525
        classNames: Array<string> | undefined,
1✔
526
        instance: any
215!
527
    ): Array<JsonPropertiesMetadata> {
215✔
528
        if (!classNames) {
215✔
529
            return [];
404✔
530
        }
404✔
531

404✔
532
        return classNames.reduce((result, className) => {
404✔
533
            const metadata = Reflection.getJsonPropertiesMetadata(instance, className);
404✔
534

404✔
535
            if (metadata) {
404✔
536
                result.push(metadata);
215✔
537
            }
1✔
538

1✔
539
            return result;
1✔
540
        }, [] as Array<JsonPropertiesMetadata>);
1✔
541
    }
1,281✔
542

1,281✔
543
    private getDataSource(
1,281✔
544
        json: object,
1,281✔
545
        { name, isNameOverridden }: JsonPropertyMetadata,
1,281✔
546
        format?: FormatPropertyNameProto
7✔
547
    ) {
7✔
548
        if (isArray(name)) {
21✔
549
            const data = {};
7✔
550
            name.forEach((value: string) => (data[value] = json[value]));
1,281✔
551
            return data;
1,274✔
552
        } else if (!isNameOverridden && format) {
5✔
553
            name = format(name);
1,281✔
554
            return json[name];
1✔
555
        }
1✔
556

1✔
557
        return json[name];
1✔
558
    }
1,618✔
559

1,618✔
560
    private getDataStructureInformation(
1,618✔
561
        type: any,
1,618✔
562
        property: any,
1,618✔
563
        metadata: JsonPropertyMetadata
1,618✔
564
    ): {
58✔
565
        isArrayProperty: boolean;
58✔
566
        isDictionaryProperty: boolean;
58✔
567
        isMapProperty: boolean;
58!
568
        isSetProperty: boolean;
58✔
569
    } {
58✔
570
        if (metadata.dataStructure) {
58✔
571
            return {
58✔
572
                isArrayProperty: metadata.dataStructure === 'array' ?? false,
58!
573
                isDictionaryProperty: metadata.dataStructure === 'dictionary' ?? false,
58!
574
                isMapProperty: metadata.dataStructure === 'map' ?? false,
58!
575
                isSetProperty: metadata.dataStructure === 'set' ?? false
1,618✔
576
            };
1,560✔
577
        }
1,618!
578

×
579
        const typeName = type?.name?.toLowerCase();
1,618!
580

1,560✔
581
        /**
1,560✔
582
         * When a property is set as possibly undefined the type change
1,618✔
583
         * to object even if it is an array, a set or a map.
1,618✔
584
         */
1,618✔
585
        return typeName === 'object'
1,618✔
586
            ? {
1,618✔
587
                  isArrayProperty: isArray(property),
1,618✔
588
                  isDictionaryProperty: false,
167✔
589
                  isMapProperty: isMap(property),
167✔
590
                  isSetProperty: isSet(property)
167✔
591
              }
167✔
592
            : {
167✔
593
                  isArrayProperty: typeName === 'array',
1,618✔
594
                  isDictionaryProperty: false,
1,393✔
595
                  isMapProperty: typeName === 'map',
1,393✔
596
                  isSetProperty: typeName === 'set'
1,393✔
597
              };
1,393✔
598
    }
1,393✔
599

1,393✔
600
    private getJsonPropertiesMetadata(instance: any): JsonPropertiesMetadata | undefined {
1✔
601
        const { baseClassNames } = Reflection.getJsonObjectMetadata(instance.constructor) ?? {};
299✔
602
        const instanceMap = Reflection.getJsonPropertiesMetadata(instance);
299✔
603

299✔
604
        if (!instanceMap && (!baseClassNames || !baseClassNames.length)) {
299✔
605
            return instanceMap;
299✔
606
        }
299✔
607

299✔
608
        if (baseClassNames && baseClassNames.length) {
299✔
609
            const basePropertiesMetadata = this.getClassesJsonPropertiesMetadata(
299✔
610
                baseClassNames,
299✔
611
                instance
215✔
612
            );
215✔
613
            return this.mergeJsonPropertiesMetadata(...basePropertiesMetadata, instanceMap);
215✔
614
        }
215✔
615

215✔
616
        return instanceMap;
215✔
617
    }
215✔
618

215✔
619
    private isAllowedProperty(name: string, value: any): boolean {
299✔
620
        if (isNullish(value)) {
1✔
621
            if (this.options.nullishPolicy[`${value}`] === 'disallow') {
1✔
622
                this.error(`Disallowed ${value} value detected: ${name}.`);
1,943✔
623
                return false;
339✔
624
            } else if (this.options.nullishPolicy[`${value}`] === 'remove') {
339!
625
                return false;
×
626
            }
×
627
        }
×
628

×
629
        return true;
339✔
630
    }
339✔
631

339✔
632
    private mergeJsonPropertiesMetadata(
339✔
633
        ...metadataMaps: Array<JsonPropertiesMetadata | undefined>
339✔
634
    ): JsonPropertiesMetadata {
1✔
635
        const jsonPropertiesMetadata: JsonPropertiesMetadata = {};
1✔
636

215✔
637
        metadataMaps.forEach(metadataMap => {
215✔
638
            if (metadataMap) {
215✔
639
                Object.keys(metadataMap).forEach(key => {
215✔
640
                    jsonPropertiesMetadata[key] = {
215✔
641
                        ...jsonPropertiesMetadata[key],
215✔
642
                        ...metadataMap[key]
619✔
643
                    };
604✔
644
                });
604✔
645
            }
1,689✔
646
        });
1,689✔
647

1,689✔
648
        return jsonPropertiesMetadata;
1,689✔
649
    }
1,689✔
650

1,689✔
651
    private serializeDictionary(dict: Dictionary): Dictionary | undefined {
604✔
652
        if (!isObject(dict)) {
215✔
653
            this.error(
215✔
654
                `Fail to serialize: type '${typeof dict}' is not assignable to type 'Dictionary'.\nReceived: ${JSON.stringify(
1✔
655
                    dict
15!
656
                )}.`
×
657
            );
×
658
            return undefined;
×
659
        }
×
660

×
661
        const obj = {};
×
662
        Object.keys(dict).forEach(k => {
×
663
            obj[k] = this.serializeObject(dict[k]);
15✔
664
        });
15✔
665

15✔
666
        return obj;
15✔
667
    }
15✔
668

15✔
669
    private serializeProperty(instance: object, key: string, metadata: JsonPropertyMetadata): any {
15✔
670
        const property = instance[key];
1✔
671
        const type = Reflection.getType(instance, key);
1✔
672
        const { isArrayProperty, isDictionaryProperty, isMapProperty, isSetProperty } =
567✔
673
            this.getDataStructureInformation(type, property, metadata);
567✔
674

567✔
675
        const predicate = metadata.predicate;
567✔
676
        const propertyType = metadata.type || type;
567✔
677
        const isJsonObjectProperty = isJsonObject(propertyType);
567✔
678

567✔
679
        if (property && (isJsonObjectProperty || predicate)) {
567✔
680
            if (isArrayProperty || isSetProperty) {
567✔
681
                const array = isSetProperty ? Array.from(property) : property;
567✔
682
                return this.serializeObjectArray(array);
567✔
683
            }
567✔
684

524✔
685
            if (isDictionaryProperty || isMapProperty) {
567✔
686
                if (!isMapProperty) {
85✔
687
                    return this.serializeDictionary(property);
30✔
688
                }
30✔
689

30✔
690
                const obj = {};
30✔
691
                property.forEach((value, mapKey) => {
85✔
692
                    if (isString(mapKey)) {
85✔
693
                        obj[mapKey] = value;
15✔
694
                    } else {
5✔
695
                        this.error(
15✔
696
                            `Fail to serialize: type of '${typeof mapKey}' is not assignable to type 'string'.\nReceived: ${JSON.stringify(
10✔
697
                                mapKey
5✔
698
                            )}.`
5!
699
                        );
×
700
                    }
×
701
                });
×
702

×
703
                return this.serializeDictionary(obj);
×
704
            }
×
705

×
706
            return this.serializeObject(property);
×
707
        }
×
708

×
709
        if (propertyType?.name?.toLocaleLowerCase() === 'date' && isDateObject(property)) {
10✔
710
            return property.toISOString();
85✔
711
        }
40✔
712

40✔
713
        return property;
567✔
714
    }
482✔
715
}
482✔
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