@@ -174,20 +174,20 @@ export function endsWith(input: string, suffix: string): boolean {
174
174
* The following rules will be applied:
175
175
* 1. If the value is null or undefined, it will be returned as is.
176
176
* 2. If the value is a function or unsupported type it will be return undefined.
177
- * 3. If the value is an array, it will be pruned to the specified depth and truncated .
177
+ * 3. If the value is an array, it will be pruned to the specified depth.
178
178
* 4. If the value is an object, it will be pruned to the specified depth and
179
179
* a. If the object is a Circular Reference it will return undefined.
180
180
* b. If the object is a Map, it will be converted to an object. Some data loss might occur if map keys are object types as last in wins.
181
181
* c. If the object is a Set, it will be converted to an array.
182
182
* d. If the object contains prototype properties, they will be picked up.
183
183
* e. If the object contains a toJSON function, it will be called and it's value will be normalized.
184
- * f. If the object is is uniterable and not clonable (e.g., WeakMap, WeakSet, etc.), it will return undefined.
184
+ * f. If the object is is uniterable and not cloneable (e.g., WeakMap, WeakSet, etc.), it will return undefined.
185
185
* g. If a symbol property is encountered, it will be converted to a string representation and could overwrite existing object keys.
186
186
* 5. If the value is an Error, we will treat it as an object.
187
- * 6. If the value is a primitive, it will be returned as is unless it is a string could be truncated .
187
+ * 6. If the value is a primitive, it will be returned as is.
188
188
* 7. If the value is a Regexp, Symbol we will convert it to the string representation.
189
189
* 8. BigInt and other typed arrays will be converted to a string unless number type works.
190
- * 9. All other values will be returned as undedfined (E.g., Buffer, DataView, Promises, Generators etc..)
190
+ * 9. All other values will be returned as undefined (E.g., Buffer, DataView, Promises, Generators etc..)
191
191
*/
192
192
export function prune ( value : unknown , depth : number = 10 ) : unknown {
193
193
function isUnsupportedType ( value : unknown ) : boolean {
@@ -236,12 +236,21 @@ export function prune(value: unknown, depth: number = 10): unknown {
236
236
return value ;
237
237
}
238
238
239
- if ( value instanceof RegExp ) {
240
- return value . toString ( ) ;
239
+ if ( value instanceof Date ) {
240
+ return value ;
241
241
}
242
242
243
243
if ( value instanceof Map ) {
244
- return Object . fromEntries ( value . entries ( ) ) ;
244
+ const result : Record < PropertyKey , unknown > = { } ;
245
+ for ( const [ key , val ] of value ) {
246
+ result [ key ] = val ;
247
+ }
248
+
249
+ return result ;
250
+ }
251
+
252
+ if ( value instanceof RegExp ) {
253
+ return value . toString ( ) ;
245
254
}
246
255
247
256
if ( value instanceof Set ) {
@@ -284,10 +293,6 @@ export function prune(value: unknown, depth: number = 10): unknown {
284
293
285
294
const normalizedValue = normalizeValue ( value ) ;
286
295
if ( typeof normalizedValue === "object" ) {
287
- if ( normalizedValue instanceof Date ) {
288
- return normalizedValue ;
289
- }
290
-
291
296
if ( currentDepth == maxDepth ) {
292
297
return undefined ;
293
298
}
@@ -298,6 +303,10 @@ export function prune(value: unknown, depth: number = 10): unknown {
298
303
return normalizedValue . map ( e => pruneImpl ( e , maxDepth , depth , seen , true ) ) ;
299
304
}
300
305
306
+ if ( normalizedValue instanceof Date ) {
307
+ return normalizedValue ;
308
+ }
309
+
301
310
// Check for circular references
302
311
if ( Object . prototype . toString . call ( normalizedValue ) === "[object Object]" ) {
303
312
if ( seen . has ( normalizedValue as object ) ) {
@@ -307,17 +316,19 @@ export function prune(value: unknown, depth: number = 10): unknown {
307
316
seen . add ( normalizedValue as object ) ;
308
317
}
309
318
310
- const result : Record < PropertyKey , unknown > = { } ;
319
+ const keys = new Set < PropertyKey > ( [ ...Object . getOwnPropertyNames ( normalizedValue ) , ...Object . getOwnPropertySymbols ( normalizedValue ) ] ) ;
320
+ // Loop over and add any inherited prototype properties
311
321
for ( const key in normalizedValue ) {
312
- const objectValue = ( normalizedValue as { [ index : PropertyKey ] : unknown } ) [ key ] ;
313
- result [ key ] = pruneImpl ( objectValue , maxDepth , currentDepth + 1 , seen ) ;
322
+ keys . add ( key ) ;
314
323
}
315
324
316
- for ( const symbolKey of Object . getOwnPropertySymbols ( normalizedValue ) ) {
325
+ type NonSymbolPropertyKey = Exclude < PropertyKey , symbol > ;
326
+ const result : Record < NonSymbolPropertyKey , unknown > = { } ;
327
+ for ( const key of keys ) {
317
328
// Normalize the key so Symbols are converted to strings.
318
- const normalizedKey = normalizeValue ( symbolKey ) as PropertyKey ;
329
+ const normalizedKey = normalizeValue ( key ) as NonSymbolPropertyKey ;
319
330
320
- const objectValue = ( normalizedValue as { [ index : PropertyKey ] : unknown } ) [ symbolKey ] ;
331
+ const objectValue = ( normalizedValue as { [ index : PropertyKey ] : unknown } ) [ key ] ;
321
332
result [ normalizedKey ] = pruneImpl ( objectValue , maxDepth , currentDepth + 1 , seen ) ;
322
333
}
323
334
@@ -349,126 +360,6 @@ export function stringify(data: unknown, exclusions?: string[], maxDepth: number
349
360
return stringifyImpl ( prunedData , exclusions || [ ] ) ;
350
361
}
351
362
352
- // https://stackoverflow.com/questions/8024149/is-it-possible-to-get-the-non-enumerable-inherited-property-names-of-an-object
353
- // function getAllKeysConditionally(obj: object, includeSelf = true, includePrototypeChain = true, includeTop = false, includeEnumerables = true, includeNonenumerables = true, includeStrings = true, includeSymbols = true) {
354
- //
355
- // // Boolean (mini-)functions to determine unknown given key's eligibility:
356
- // const isEnumerable = (obj: object, key: PropertyKey) => Object.propertyIsEnumerable.call(obj, key);
357
- // const isString = (key: PropertyKey) => typeof key === 'string';
358
- // const isSymbol = (key: PropertyKey) => typeof key === 'symbol';
359
- // const includeBasedOnEnumerability = (obj: object, key: PropertyKey) => (includeEnumerables && isEnumerable(obj, key)) || (includeNonenumerables && !isEnumerable(obj, key));
360
- // const includeBasedOnKeyType = (key: PropertyKey) => (includeStrings && isString(key)) || (includeSymbols && isSymbol(key));
361
- // const include = (obj: object, key: PropertyKey) => includeBasedOnEnumerability(obj, key) && includeBasedOnKeyType(key);
362
- // const notYetRetrieved = (keys: PropertyKey[], key: PropertyKey) => !keys.includes(key);
363
- //
364
- // // filter function putting all the above together:
365
- // const filterFn = (key: PropertyKey) => notYetRetrieved(keys, key) && include(obj, key);
366
- //
367
- // // conditional chooses one of two functions to determine whether to exclude the top level or not:
368
- // const stopFn = includeTop ? ((obj: unknown) => obj === null) : ((obj: unknown) => Object.getPrototypeOf(obj) === null);
369
- //
370
- // // and now the loop to collect and filter everything:
371
- // let keys: PropertyKey[] = [];
372
- // while (!stopFn(obj)) {
373
- // if (includeSelf) {
374
- // const ownKeys = Reflect.ownKeys(obj).filter(filterFn);
375
- // keys = keys.concat(ownKeys);
376
- // }
377
- // if (!includePrototypeChain) { break; }
378
- // else {
379
- // includeSelf = true;
380
- // obj = Object.getPrototypeOf(obj);
381
- // }
382
- // }
383
- // return keys;
384
- // }
385
-
386
- // export function stringify(data: unknown, exclusions?: string[], maxDepth?: number = Infinity): string | undefined {
387
- // function stringifyImpl(obj: unknown, excludedKeys: string[], maxDepth?: number = Infinity, currentDepth: number, cache: WeakSet): string | undefined {
388
- // if (currentDepth > maxDepth) {
389
- // return;
390
- // }
391
- //
392
- // if (typeof obj === "function" || obj === undefined) {
393
- // return undefined;
394
- // }
395
- //
396
- // if (obj === null) {
397
- // return "null";
398
- // }
399
- //
400
- // if (typeof obj === "string") {
401
- // return `"${obj}"`;
402
- // }
403
- //
404
- // if (typeof obj === "number" || typeof obj === "boolean") {
405
- // return obj.toString();
406
- // }
407
- //
408
- // if (obj instanceof Date) {
409
- // return `"${obj.toISOString()}"`;
410
- // }
411
- //
412
- // if (obj instanceof RegExp) {
413
- // return obj.toString();
414
- // }
415
- //
416
- // if (typeof obj === "symbol") {
417
- // return obj.toString();
418
- // }
419
- //
420
- // if (typeof obj === "bigint") {
421
- // return obj.toString() + "n";
422
- // }
423
- //
424
- // if (typeof obj === "object") {
425
- // if (Array.isArray(obj)) {
426
- // const items = obj.map(item => stringify(item, maxDepth, currentDepth + 1));
427
- // return `[${items.join(",")}]`;
428
- // } else {
429
- // const keys = Object.keys(obj).sort(); // sort keys alphabetically
430
- // const items = keys.map(key => `"${key}":${stringify(obj[key], maxDepth, currentDepth + 1) || "null"}`);
431
- // return `{${items.join(",")}}`;
432
- // }
433
- // }
434
- //
435
- // // handle circular references
436
- // if (cache.indexOf(obj)) {
437
- // return "[Circular Reference]";
438
- // }
439
- // cache.push(obj);
440
- //
441
- // // handle objects that define their own toJSON method
442
- // const toJSON = obj.toJSON;
443
- // if (typeof toJSON === "function") {
444
- // return stringify(toJSON.call(obj), maxDepth, currentDepth + 1);
445
- // }
446
- //
447
- // // handle objects that implement the iterator protocol (Maps, Sets, etc.)
448
- // const iterator = obj[Symbol.iterator];
449
- // if (typeof iterator === "function") {
450
- // const items = [];
451
- // for (let item of obj) {
452
- // items.push(stringify(item, maxDepth, currentDepth + 1));
453
- // }
454
- // return `[${items.join(",")}]`;
455
- // }
456
- //
457
- // // handle objects that implement the async iterator protocol (Streams, etc.)
458
- // const asyncIterator = obj[Symbol.asyncIterator];
459
- // if (typeof asyncIterator === "function") {
460
- // return stringify(asyncIterator.call(obj).next().then(result => {
461
- // if (result.done) {
462
- // return null;
463
- // }
464
- // return stringify(result.value, maxDepth, currentDepth + 1);
465
- // }), maxDepth, currentDepth + 1);
466
- // }
467
- //
468
- // return undefined;
469
- // }
470
- // }
471
-
472
363
export function toBoolean ( input : unknown , defaultValue : boolean = false ) : boolean {
473
364
if ( typeof input === "boolean" ) {
474
365
return input ;
0 commit comments