@@ -4,7 +4,7 @@ import { ExtendedError, WrappedFunction } from '@sentry/types';
44
55import { htmlTreeAsString } from './browser' ;
66import { isElement , isError , isEvent , isInstanceOf , isPlainObject , isPrimitive , isSyntheticEvent } from './is' ;
7- import { memoBuilder , MemoFunc } from './memo' ;
7+ import { memoBuilder } from './memo' ;
88import { getFunctionName } from './stacktrace' ;
99import { truncate } from './string' ;
1010
@@ -300,85 +300,85 @@ function makeSerializable<T>(value: T, key?: any): T | string {
300300 return value ;
301301}
302302
303+ type UnknownMaybeToJson = unknown & { toJSON ?: ( ) => string } ;
304+
303305/**
304- * Walks an object to perform a normalization on it
306+ * normalize()
305307 *
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
310317 */
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 ( ) ;
314320
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+ }
319326
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+ }
326331
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+ }
334339
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 ) ;
338343
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 ) ? [ ] : { } ;
341346
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+ }
346351
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 ) ;
352370 }
353- // Recursively walk through all the child nodes
354- const innerValue : any = source [ innerKey ] ;
355- acc [ innerKey ] = walk ( innerKey , innerValue , depth - 1 , memo ) ;
356- }
357371
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 ) ;
360374
361- // Return accumulated values
362- return acc ;
363- }
375+ // Return accumulated values
376+ return acc ;
377+ }
364378
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 {
379379 try {
380380 // since we're at the outermost level, there is no key
381- return walk ( '' , input , depth ) ;
381+ return walk ( '' , input as UnknownMaybeToJson , depth ) ;
382382 } catch ( _oO ) {
383383 return '**non-serializable**' ;
384384 }
0 commit comments