@@ -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 } from './memo' ;
7+ import { memoBuilder , MemoFunc } from './memo' ;
88import { getFunctionName } from './stacktrace' ;
99import { truncate } from './string' ;
1010
@@ -302,6 +302,81 @@ function makeSerializable<T>(value: T, key?: any): T | string {
302302
303303type UnknownMaybeWithToJson = unknown & { toJSON ?: ( ) => string } ;
304304
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 , maxProperties , memo ) ;
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+
305380/**
306381 * Recursively normalizes the given object.
307382 *
@@ -317,74 +392,14 @@ type UnknownMaybeWithToJson = unknown & { toJSON?: () => string };
317392 *
318393 * @param input The object to be normalized.
319394 * @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
321396 * 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 .
323398 */
324399export 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-
385400 try {
386401 // 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 ) ;
388403 } catch ( _oO ) {
389404 return '**non-serializable**' ;
390405 }
0 commit comments