@@ -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/**
@@ -203,61 +204,42 @@ export function extractExceptionKeysForMessage(exception: Record<string, unknown
203204}
204205
205206/**
206- * Given any object, return a new object having removed all fields whose value was `undefined`.
207+ * Given any object, return the new object with removed keys that value was `undefined`.
207208 * Works recursively on objects and arrays.
208209 *
209210 * Attention: This function keeps circular references in the returned object.
210211 */
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-
212+ export function dropUndefinedKeys < T > ( val : T ) : T {
217213 // This function just proxies `_dropUndefinedKeys` to keep the `memoBuilder` out of this function's API
218- return _dropUndefinedKeys ( inputValue , memoizationMap ) ;
214+ return _dropUndefinedKeys ( val , memoBuilder ( ) ) ;
219215}
220216
221- function _dropUndefinedKeys < T > ( inputValue : T , memoizationMap : Map < unknown , unknown > ) : T {
222- if ( isPlainObject ( inputValue ) ) {
223- // If this node has already been visited due to a circular reference, return the object it was mapped to in the new object
224- const memoVal = memoizationMap . get ( inputValue ) ;
225- if ( memoVal !== undefined ) {
226- return memoVal as T ;
227- }
228-
229- const returnValue : { [ key : string ] : any } = { } ;
230- // Store the mapping of this value in case we visit it again, in case of circular data
231- memoizationMap . set ( inputValue , returnValue ) ;
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
232219
233- for ( const key of Object . keys ( inputValue ) ) {
234- if ( typeof inputValue [ key ] !== 'undefined' ) {
235- returnValue [ key ] = _dropUndefinedKeys ( inputValue [ key ] , memoizationMap ) ;
220+ if ( isPlainObject ( val ) ) {
221+ if ( memoize ( val ) ) {
222+ return val ;
223+ }
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 ) ;
236228 }
237229 }
238-
239- return returnValue as T ;
230+ return rv as T ;
240231 }
241232
242- if ( Array . isArray ( inputValue ) ) {
243- // If this node has already been visited due to a circular reference, return the array it was mapped to in the new object
244- const memoVal = memoizationMap . get ( inputValue ) ;
245- if ( memoVal !== undefined ) {
246- return memoVal as T ;
233+ if ( Array . isArray ( val ) ) {
234+ if ( memoize ( val ) ) {
235+ return val ;
247236 }
248-
249- const returnValue : unknown [ ] = [ ] ;
250- // Store the mapping of this value in case we visit it again, in case of circular data
251- memoizationMap . set ( inputValue , returnValue ) ;
252-
253- inputValue . forEach ( ( item : unknown ) => {
254- returnValue . push ( _dropUndefinedKeys ( item , memoizationMap ) ) ;
255- } ) ;
256-
257- return returnValue as unknown as T ;
237+ return ( val as any [ ] ) . map ( item => {
238+ return _dropUndefinedKeys ( item , memo ) ;
239+ } ) as any ;
258240 }
259241
260- return inputValue ;
242+ return val ;
261243}
262244
263245/**
0 commit comments