@@ -177,10 +177,12 @@ export function endsWith(input: string, suffix: string): boolean {
177
177
* 3. If the value is an array, it will be pruned to the specified depth and truncated.
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
- * b. If the object is a Map, it will be converted to an object.
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
- * e. If the object is is uniterable and not clonable (e.g., WeakMap, WeakSet, etc.), it will return undefined.
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.
185
+ * g. If a symbol property is encountered, it will be converted to a string representation and could overwrite existing object keys.
184
186
* 5. If the value is an Error, we will treat it as an object.
185
187
* 6. If the value is a primitive, it will be returned as is unless it is a string could be truncated.
186
188
* 7. If the value is a Regexp, Symbol we will convert it to the string representation.
@@ -221,6 +223,10 @@ export function prune(value: unknown, depth: number = 10): unknown {
221
223
}
222
224
223
225
function normalizeValue ( value : unknown ) : unknown {
226
+ function hasToJSONFunction ( value : unknown ) : value is { toJSON : ( ) => unknown } {
227
+ return value !== null && typeof value === "object" && typeof ( value as { toJSON ?: unknown } ) . toJSON === "function" ;
228
+ }
229
+
224
230
if ( typeof value === "bigint" ) {
225
231
return `${ value . toString ( ) } n` ;
226
232
}
@@ -248,6 +254,11 @@ export function prune(value: unknown, depth: number = 10): unknown {
248
254
return Array . from ( value as Iterable < unknown > ) ;
249
255
}
250
256
257
+ if ( hasToJSONFunction ( value ) ) {
258
+ // NOTE: We are not checking for circular references or overflow
259
+ return normalizeValue ( value . toJSON ( ) ) ;
260
+ }
261
+
251
262
return value ;
252
263
}
253
264
@@ -258,7 +269,7 @@ export function prune(value: unknown, depth: number = 10): unknown {
258
269
return value ;
259
270
}
260
271
261
- function pruneImpl ( value : unknown , maxDepth : number , currentDepth : number = 10 , seen : WeakSet < object > = new WeakSet ( ) ) : unknown {
272
+ function pruneImpl ( value : unknown , maxDepth : number , currentDepth : number = 10 , seen : WeakSet < object > = new WeakSet ( ) , parentIsArray : boolean = false ) : unknown {
262
273
if ( value === null || value === undefined ) {
263
274
return value ;
264
275
}
@@ -277,8 +288,14 @@ export function prune(value: unknown, depth: number = 10): unknown {
277
288
return normalizedValue ;
278
289
}
279
290
291
+ if ( currentDepth == maxDepth ) {
292
+ return undefined ;
293
+ }
294
+
280
295
if ( Array . isArray ( normalizedValue ) ) {
281
- return normalizedValue . map ( e => pruneImpl ( e , maxDepth , currentDepth + 1 , seen ) ) ;
296
+ // Treat an object inside of an array as a single level
297
+ const depth : number = parentIsArray ? currentDepth + 1 : currentDepth ;
298
+ return normalizedValue . map ( e => pruneImpl ( e , maxDepth , depth , seen , true ) ) ;
282
299
}
283
300
284
301
// Check for circular references
@@ -291,9 +308,17 @@ export function prune(value: unknown, depth: number = 10): unknown {
291
308
}
292
309
293
310
const result : Record < PropertyKey , unknown > = { } ;
294
- for ( const key in value ) {
295
- const val = ( value as { [ index : PropertyKey ] : unknown } ) [ key ] ;
296
- result [ key ] = pruneImpl ( val , maxDepth , currentDepth + 1 , seen ) ;
311
+ for ( const key in normalizedValue ) {
312
+ const objectValue = ( normalizedValue as { [ index : PropertyKey ] : unknown } ) [ key ] ;
313
+ result [ key ] = pruneImpl ( objectValue , maxDepth , currentDepth + 1 , seen ) ;
314
+ }
315
+
316
+ for ( const symbolKey of Object . getOwnPropertySymbols ( normalizedValue ) ) {
317
+ // Normalize the key so Symbols are converted to strings.
318
+ const normalizedKey = normalizeValue ( symbolKey ) as PropertyKey ;
319
+
320
+ const objectValue = ( normalizedValue as { [ index : PropertyKey ] : unknown } ) [ symbolKey ] ;
321
+ result [ normalizedKey ] = pruneImpl ( objectValue , maxDepth , currentDepth + 1 , seen ) ;
297
322
}
298
323
299
324
return result ;
0 commit comments