@@ -4,7 +4,6 @@ import { WrappedFunction } from '@sentry/types';
4
4
5
5
import { htmlTreeAsString } from './browser' ;
6
6
import { isElement , isError , isEvent , isInstanceOf , isPlainObject , isPrimitive } from './is' ;
7
- import { memoBuilder , MemoFunc } from './memo' ;
8
7
import { truncate } from './string' ;
9
8
10
9
/**
@@ -204,42 +203,57 @@ export function extractExceptionKeysForMessage(exception: Record<string, unknown
204
203
}
205
204
206
205
/**
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`.
208
207
* Works recursively on objects and arrays.
209
208
*
210
209
* Attention: This function keeps circular references in the returned object.
211
210
*/
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
+
213
217
// 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 ) ;
215
219
}
216
220
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 ;
223
226
}
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 ) ;
228
234
}
229
235
}
230
- return rv as T ;
236
+
237
+ return returnValue as T ;
231
238
}
232
239
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 ;
236
244
}
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 ;
240
254
}
241
255
242
- return val ;
256
+ return inputValue ;
243
257
}
244
258
245
259
/**
0 commit comments