@@ -4,7 +4,6 @@ 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' ;
87import { truncate } from './string' ;
98
109/**
@@ -204,42 +203,57 @@ export function extractExceptionKeysForMessage(exception: Record<string, unknown
204203}
205204
206205/**
207- * Given any object, return the new object with removed keys that value was `undefined`.
206+ * Given any object, return a new object having removed all fields whose value was `undefined`.
208207 * Works recursively on objects and arrays.
209208 *
210209 * Attention: This function keeps circular references in the returned object.
211210 */
212- export function dropUndefinedKeys < T > ( val : T ) : T {
211+ export function dropUndefinedKeys < T > ( inputValue : T ) : T {
212+ // This map keeps track of what already visited nodes map to.
213+ // Our Set - based memoBuilder doesn't work here because we want to the output object to have the same circular
214+ // references as the input object.
215+ const memoizationMap = new Map < unknown , unknown > ( ) ;
216+
213217 // This function just proxies `_dropUndefinedKeys` to keep the `memoBuilder` out of this function's API
214- return _dropUndefinedKeys ( val , memoBuilder ( ) ) ;
218+ return _dropUndefinedKeys ( inputValue , memoizationMap ) ;
215219}
216220
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-
220- if ( isPlainObject ( val ) ) {
221- if ( memoize ( val ) ) {
222- return val ;
221+ function _dropUndefinedKeys < T > ( inputValue : T , memoizationMap : Map < unknown , unknown > ) : T {
222+ if ( isPlainObject ( inputValue ) ) {
223+ const memoVal = memoizationMap . get ( inputValue ) ;
224+ if ( memoVal !== undefined ) {
225+ return memoVal as T ;
223226 }
224- const rv : { [ key : string ] : any } = { } ;
225- for ( const key of Object . keys ( val ) ) {
226- if ( typeof val [ key ] !== 'undefined' ) {
227- rv [ key ] = _dropUndefinedKeys ( val [ key ] , memo ) ;
227+
228+ const returnValue : { [ key : string ] : any } = { } ;
229+ memoizationMap . set ( inputValue , returnValue ) ;
230+
231+ for ( const key of Object . keys ( inputValue ) ) {
232+ if ( typeof inputValue [ key ] !== 'undefined' ) {
233+ returnValue [ key ] = _dropUndefinedKeys ( inputValue [ key ] , memoizationMap ) ;
228234 }
229235 }
230- return rv as T ;
236+
237+ return returnValue as T ;
231238 }
232239
233- if ( Array . isArray ( val ) ) {
234- if ( memoize ( val ) ) {
235- return val ;
240+ if ( Array . isArray ( inputValue ) ) {
241+ const memoVal = memoizationMap . get ( inputValue ) ;
242+ if ( memoVal !== undefined ) {
243+ return memoVal as T ;
236244 }
237- return ( val as any [ ] ) . map ( item => {
238- return _dropUndefinedKeys ( item , memo ) ;
239- } ) as any ;
245+
246+ const returnValue : unknown [ ] = [ ] ;
247+ memoizationMap . set ( inputValue , returnValue ) ;
248+
249+ inputValue . forEach ( ( item : unknown ) => {
250+ returnValue . push ( _dropUndefinedKeys ( item , memoizationMap ) ) ;
251+ } ) ;
252+
253+ return returnValue as unknown as T ;
240254 }
241255
242- return val ;
256+ return inputValue ;
243257}
244258
245259/**
0 commit comments