@@ -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 , MemoFunc } from './memo' ;
7
+ import { memoBuilder } from './memo' ;
8
8
import { getFunctionName } from './stacktrace' ;
9
9
import { truncate } from './string' ;
10
10
@@ -300,85 +300,85 @@ function makeSerializable<T>(value: T, key?: any): T | string {
300
300
return value ;
301
301
}
302
302
303
+ type UnknownMaybeToJson = unknown & { toJSON ?: ( ) => string } ;
304
+
303
305
/**
304
- * Walks an object to perform a normalization on it
306
+ * normalize()
305
307
*
306
- * @param key of object that's walked in current iteration
307
- * @param value object to be walked
308
- * @param depth Optional number indicating how deep should walking be performed
309
- * @param memo Optional Memo class handling decycling
308
+ * - Creates a copy to prevent original input mutation
309
+ * - Skip non-enumerable
310
+ * - Calls `toJSON` if implemented
311
+ * - Removes circular references
312
+ * - Translates non-serializable values (undefined/NaN/Functions) to serializable format
313
+ * - Translates known global objects/Classes to a string representations
314
+ * - Takes care of Error objects serialization
315
+ * - Optionally limit depth of final output
316
+ * - Optionally limit max number of properties/elements for each object/array
310
317
*/
311
- // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
312
- export function walk ( key : string , value : any , depth : number = + Infinity , memo : MemoFunc = memoBuilder ( ) ) : any {
313
- const [ memoize , unmemoize ] = memo ;
318
+ export function normalize ( input : unknown , depth : number = + Infinity , maxProperties : number = + Infinity ) : any {
319
+ const [ memoize , unmemoize ] = memoBuilder ( ) ;
314
320
315
- // If we reach the maximum depth, serialize whatever is left
316
- if ( depth === 0 ) {
317
- return serializeValue ( value ) ;
318
- }
321
+ function walk ( key : string , value : UnknownMaybeToJson , depth : number = + Infinity ) : unknown {
322
+ // If we reach the maximum depth, serialize whatever is left
323
+ if ( depth === 0 ) {
324
+ return serializeValue ( value ) ;
325
+ }
319
326
320
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
321
- // If value implements `toJSON` method, call it and return early
322
- if ( value !== null && value !== undefined && typeof value . toJSON === 'function' ) {
323
- return value . toJSON ( ) ;
324
- }
325
- /* eslint-enable @typescript-eslint/no-unsafe-member-access */
327
+ // If value implements `toJSON` method, call it and return early
328
+ if ( value !== null && value !== undefined && typeof value . toJSON === 'function' ) {
329
+ return value . toJSON ( ) ;
330
+ }
326
331
327
- // `makeSerializable` provides a string representation of certain non-serializable values. For all others, it's a
328
- // pass-through. If what comes back is a primitive (either because it's been stringified or because it was primitive
329
- // all along), we're done.
330
- const serializable = makeSerializable ( value , key ) ;
331
- if ( isPrimitive ( serializable ) ) {
332
- return serializable ;
333
- }
332
+ // `makeSerializable` provides a string representation of certain non-serializable values. For all others, it's a
333
+ // pass-through. If what comes back is a primitive (either because it's been stringified or because it was primitive
334
+ // all along), we're done.
335
+ const serializable = makeSerializable ( value , key ) ;
336
+ if ( isPrimitive ( serializable ) ) {
337
+ return serializable ;
338
+ }
334
339
335
- // Create source that we will use for the next iteration. It will either be an objectified error object (`Error` type
336
- // with extracted key:value pairs) or the input itself.
337
- const source = getWalkSource ( value ) ;
340
+ // Create source that we will use for the next iteration. It will either be an objectified error object (`Error` type
341
+ // with extracted key:value pairs) or the input itself.
342
+ const source = getWalkSource ( value ) ;
338
343
339
- // Create an accumulator that will act as a parent for all future itterations of that branch
340
- const acc : { [ key : string ] : any } = Array . isArray ( value ) ? [ ] : { } ;
344
+ // Create an accumulator that will act as a parent for all future itterations of that branch
345
+ const acc : { [ key : string ] : any } = Array . isArray ( value ) ? [ ] : { } ;
341
346
342
- // If we already walked that branch, bail out, as it's circular reference
343
- if ( memoize ( value ) ) {
344
- return '[Circular ~]' ;
345
- }
347
+ // If we already walked that branch, bail out, as it's circular reference
348
+ if ( memoize ( value ) ) {
349
+ return '[Circular ~]' ;
350
+ }
346
351
347
- // Walk all keys of the source
348
- for ( const innerKey in source ) {
349
- // Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
350
- if ( ! Object . prototype . hasOwnProperty . call ( source , innerKey ) ) {
351
- continue ;
352
+ let propertyCount = 0 ;
353
+ // Walk all keys of the source
354
+ for ( const innerKey in source ) {
355
+ // Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
356
+ if ( ! Object . prototype . hasOwnProperty . call ( source , innerKey ) ) {
357
+ continue ;
358
+ }
359
+
360
+ if ( propertyCount >= maxProperties ) {
361
+ acc [ innerKey ] = '[MaxProperties ~]' ;
362
+ break ;
363
+ }
364
+
365
+ propertyCount += 1 ;
366
+
367
+ // Recursively walk through all the child nodes
368
+ const innerValue : UnknownMaybeToJson = source [ innerKey ] ;
369
+ acc [ innerKey ] = walk ( innerKey , innerValue , depth - 1 ) ;
352
370
}
353
- // Recursively walk through all the child nodes
354
- const innerValue : any = source [ innerKey ] ;
355
- acc [ innerKey ] = walk ( innerKey , innerValue , depth - 1 , memo ) ;
356
- }
357
371
358
- // Once walked through all the branches, remove the parent from memo storage
359
- unmemoize ( value ) ;
372
+ // Once walked through all the branches, remove the parent from memo storage
373
+ unmemoize ( value ) ;
360
374
361
- // Return accumulated values
362
- return acc ;
363
- }
375
+ // Return accumulated values
376
+ return acc ;
377
+ }
364
378
365
- /**
366
- * normalize()
367
- *
368
- * - Creates a copy to prevent original input mutation
369
- * - Skip non-enumerablers
370
- * - Calls `toJSON` if implemented
371
- * - Removes circular references
372
- * - Translates non-serializeable values (undefined/NaN/Functions) to serializable format
373
- * - Translates known global objects/Classes to a string representations
374
- * - Takes care of Error objects serialization
375
- * - Optionally limit depth of final output
376
- */
377
- // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
378
- export function normalize ( input : any , depth ?: number ) : any {
379
379
try {
380
380
// since we're at the outermost level, there is no key
381
- return walk ( '' , input , depth ) ;
381
+ return walk ( '' , input as UnknownMaybeToJson , depth ) ;
382
382
} catch ( _oO ) {
383
383
return '**non-serializable**' ;
384
384
}
0 commit comments