@@ -4,7 +4,7 @@ import { ExtendedError, WrappedFunction } from '@sentry/types';
4
4
5
5
import { htmlTreeAsString } from './browser' ;
6
6
import { isElement , isError , isEvent , isInstanceOf , isPlainObject , isPrimitive , isSyntheticEvent } from './is' ;
7
- import { memoBuilder } from './memo' ;
7
+ import { memoBuilder , MemoFunc } from './memo' ;
8
8
import { getFunctionName } from './stacktrace' ;
9
9
import { truncate } from './string' ;
10
10
@@ -302,6 +302,81 @@ function makeSerializable<T>(value: T, key?: any): T | string {
302
302
303
303
type UnknownMaybeWithToJson = unknown & { toJSON ?: ( ) => string } ;
304
304
305
+ /**
306
+ * Walks an object to perform a normalization on it
307
+ *
308
+ * @param key of object that's walked in current iteration
309
+ * @param value object to be walked
310
+ * @param depth Optional number indicating how deep should walking be performed
311
+ * @param maxProperties Optional maximum number of properties/elements included in any single object/array
312
+ * @param memo Optional Memo class handling decycling
313
+ */
314
+ export function walk (
315
+ key : string ,
316
+ value : UnknownMaybeWithToJson ,
317
+ depth : number = + Infinity ,
318
+ maxProperties : number = + Infinity ,
319
+ memo : MemoFunc = memoBuilder ( ) ,
320
+ ) : unknown {
321
+ const [ memoize , unmemoize ] = memo ;
322
+
323
+ // If we reach the maximum depth, serialize whatever is left
324
+ if ( depth === 0 ) {
325
+ return serializeValue ( value ) ;
326
+ }
327
+
328
+ // If value implements `toJSON` method, call it and return early
329
+ if ( value !== null && value !== undefined && typeof value . toJSON === 'function' ) {
330
+ return value . toJSON ( ) ;
331
+ }
332
+
333
+ // `makeSerializable` provides a string representation of certain non-serializable values. For all others, it's a
334
+ // pass-through. If what comes back is a primitive (either because it's been stringified or because it was primitive
335
+ // all along), we're done.
336
+ const serializable = makeSerializable ( value , key ) ;
337
+ if ( isPrimitive ( serializable ) ) {
338
+ return serializable ;
339
+ }
340
+
341
+ // Create source that we will use for the next iteration. It will either be an objectified error object (`Error` type
342
+ // with extracted key:value pairs) or the input itself.
343
+ const source = getWalkSource ( value ) ;
344
+
345
+ // Create an accumulator that will act as a parent for all future itterations of that branch
346
+ const acc : { [ key : string ] : any } = Array . isArray ( value ) ? [ ] : { } ;
347
+
348
+ // If we already walked that branch, bail out, as it's circular reference
349
+ if ( memoize ( value ) ) {
350
+ return '[Circular ~]' ;
351
+ }
352
+
353
+ let propertyCount = 0 ;
354
+ // Walk all keys of the source
355
+ for ( const innerKey in source ) {
356
+ // Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
357
+ if ( ! Object . prototype . hasOwnProperty . call ( source , innerKey ) ) {
358
+ continue ;
359
+ }
360
+
361
+ if ( propertyCount >= maxProperties ) {
362
+ acc [ innerKey ] = '[MaxProperties ~]' ;
363
+ break ;
364
+ }
365
+
366
+ propertyCount += 1 ;
367
+
368
+ // Recursively walk through all the child nodes
369
+ const innerValue : UnknownMaybeWithToJson = source [ innerKey ] ;
370
+ acc [ innerKey ] = walk ( innerKey , innerValue , depth - 1 ) ;
371
+ }
372
+
373
+ // Once walked through all the branches, remove the parent from memo storage
374
+ unmemoize ( value ) ;
375
+
376
+ // Return accumulated values
377
+ return acc ;
378
+ }
379
+
305
380
/**
306
381
* Recursively normalizes the given object.
307
382
*
@@ -317,74 +392,14 @@ type UnknownMaybeWithToJson = unknown & { toJSON?: () => string };
317
392
*
318
393
* @param input The object to be normalized.
319
394
* @param depth The max depth to which to normalize the object. (Anything deeper stringified whole.)
320
- * @param maxProperties The max number of elements or properties to be included in any single array or
395
+ * @param maxProperties The max number of elements or properties to be included in any single array or
321
396
* object in the normallized output..
322
- * @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normaliztion .
397
+ * @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization .
323
398
*/
324
399
export function normalize ( input : unknown , depth : number = + Infinity , maxProperties : number = + Infinity ) : any {
325
- const [ memoize , unmemoize ] = memoBuilder ( ) ;
326
-
327
- function walk ( key : string , value : UnknownMaybeToJson , depth : number = + Infinity ) : unknown {
328
- // If we reach the maximum depth, serialize whatever is left
329
- if ( depth === 0 ) {
330
- return serializeValue ( value ) ;
331
- }
332
-
333
- // If value implements `toJSON` method, call it and return early
334
- if ( value !== null && value !== undefined && typeof value . toJSON === 'function' ) {
335
- return value . toJSON ( ) ;
336
- }
337
-
338
- // `makeSerializable` provides a string representation of certain non-serializable values. For all others, it's a
339
- // pass-through. If what comes back is a primitive (either because it's been stringified or because it was primitive
340
- // all along), we're done.
341
- const serializable = makeSerializable ( value , key ) ;
342
- if ( isPrimitive ( serializable ) ) {
343
- return serializable ;
344
- }
345
-
346
- // Create source that we will use for the next iteration. It will either be an objectified error object (`Error` type
347
- // with extracted key:value pairs) or the input itself.
348
- const source = getWalkSource ( value ) ;
349
-
350
- // Create an accumulator that will act as a parent for all future itterations of that branch
351
- const acc : { [ key : string ] : any } = Array . isArray ( value ) ? [ ] : { } ;
352
-
353
- // If we already walked that branch, bail out, as it's circular reference
354
- if ( memoize ( value ) ) {
355
- return '[Circular ~]' ;
356
- }
357
-
358
- let propertyCount = 0 ;
359
- // Walk all keys of the source
360
- for ( const innerKey in source ) {
361
- // Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
362
- if ( ! Object . prototype . hasOwnProperty . call ( source , innerKey ) ) {
363
- continue ;
364
- }
365
-
366
- if ( propertyCount >= maxProperties ) {
367
- acc [ innerKey ] = '[MaxProperties ~]' ;
368
- break ;
369
- }
370
-
371
- propertyCount += 1 ;
372
-
373
- // Recursively walk through all the child nodes
374
- const innerValue : UnknownMaybeToJson = source [ innerKey ] ;
375
- acc [ innerKey ] = walk ( innerKey , innerValue , depth - 1 ) ;
376
- }
377
-
378
- // Once walked through all the branches, remove the parent from memo storage
379
- unmemoize ( value ) ;
380
-
381
- // Return accumulated values
382
- return acc ;
383
- }
384
-
385
400
try {
386
401
// since we're at the outermost level, there is no key
387
- return walk ( '' , input as UnknownMaybeToJson , depth ) ;
402
+ return walk ( '' , input as UnknownMaybeWithToJson , depth , maxProperties ) ;
388
403
} catch ( _oO ) {
389
404
return '**non-serializable**' ;
390
405
}
0 commit comments