@@ -4,6 +4,7 @@ import { WrappedFunction } from '@sentry/types';
44
55import { htmlTreeAsString } from './browser' ;
66import { isElement , isError , isEvent , isInstanceOf , isPlainObject , isPrimitive } from './is' ;
7+ import { memoBuilder , MemoFunc } from './memo' ;
78import { truncate } from './string' ;
89
910/**
@@ -205,20 +206,37 @@ export function extractExceptionKeysForMessage(exception: Record<string, unknown
205206/**
206207 * Given any object, return the new object with removed keys that value was `undefined`.
207208 * Works recursively on objects and arrays.
209+ *
210+ * Attention: This function keeps circular references in the returned object.
208211 */
209212export function dropUndefinedKeys < T > ( val : T ) : T {
213+ // This function just proxies `_dropUndefinedKeys` to keep the `memoBuilder` out of this function's API
214+ return _dropUndefinedKeys ( val , memoBuilder ( ) ) ;
215+ }
216+
217+ function _dropUndefinedKeys < T > ( val : T , memo : MemoFunc ) : T {
218+ const [ memoize ] = memo ; // we don't need unmemoize because we don't need to visit nodes twice
219+
210220 if ( isPlainObject ( val ) ) {
221+ if ( memoize ( val ) ) {
222+ return val ;
223+ }
211224 const rv : { [ key : string ] : any } = { } ;
212225 for ( const key of Object . keys ( val ) ) {
213226 if ( typeof val [ key ] !== 'undefined' ) {
214- rv [ key ] = dropUndefinedKeys ( val [ key ] ) ;
227+ rv [ key ] = _dropUndefinedKeys ( val [ key ] , memo ) ;
215228 }
216229 }
217230 return rv as T ;
218231 }
219232
220233 if ( Array . isArray ( val ) ) {
221- return ( val as any [ ] ) . map ( dropUndefinedKeys ) as any ;
234+ if ( memoize ( val ) ) {
235+ return val ;
236+ }
237+ return ( val as any [ ] ) . map ( item => {
238+ return _dropUndefinedKeys ( item , memo ) ;
239+ } ) as any ;
222240 }
223241
224242 return val ;
0 commit comments