Skip to content

Commit 6710bac

Browse files
committed
Undo changes to dropUndefinedKeys
1 parent ec080b2 commit 6710bac

File tree

2 files changed

+41
-77
lines changed

2 files changed

+41
-77
lines changed

packages/utils/src/object.ts

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { WrappedFunction } from '@sentry/types';
44

55
import { htmlTreeAsString } from './browser';
66
import { isElement, isError, isEvent, isInstanceOf, isPlainObject, isPrimitive } from './is';
7+
import { memoBuilder, MemoFunc } from './memo';
78
import { 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
/**

packages/utils/test/object.test.ts

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -200,34 +200,28 @@ describe('dropUndefinedKeys()', () => {
200200
});
201201
});
202202

203-
test('should not throw on objects with circular reference', () => {
204-
const chicken: any = {
203+
test('objects with circular reference', () => {
204+
const dog: any = {
205205
food: undefined,
206206
};
207207

208-
const egg = {
209-
edges: undefined,
210-
contains: chicken,
208+
const human = {
209+
brain: undefined,
210+
pets: dog,
211211
};
212212

213-
chicken.lays = egg;
214-
215-
const droppedChicken = dropUndefinedKeys(chicken);
216-
217-
// Removes undefined keys
218-
expect(Object.keys(droppedChicken)).toEqual(['lays']);
219-
expect(Object.keys(droppedChicken.lays)).toEqual(['contains']);
220-
221-
// Returns new object
222-
expect(chicken === droppedChicken).toBe(false);
223-
expect(chicken.lays === droppedChicken.lays).toBe(false);
213+
const rat = {
214+
scares: human,
215+
weight: '4kg',
216+
};
224217

225-
// Returns new references within objects
226-
expect(chicken === droppedChicken.lays.contains).toBe(false);
227-
expect(egg === droppedChicken.lays.contains.lays).toBe(false);
218+
dog.chases = rat;
228219

229-
// Keeps circular reference
230-
expect(droppedChicken.lays.contains === droppedChicken).toBe(true);
220+
expect(dropUndefinedKeys(human)).toStrictEqual({
221+
pets: {
222+
chases: rat,
223+
},
224+
});
231225
});
232226

233227
test('arrays with circular reference', () => {
@@ -241,22 +235,10 @@ describe('dropUndefinedKeys()', () => {
241235

242236
egg[0] = chicken;
243237

244-
const result = dropUndefinedKeys(chicken);
245-
246-
// Removes undefined keys
247-
expect(Object.keys(result)).toEqual(['weight', 'lays']);
248-
expect(Object.keys(result.lays)).toEqual(['0']);
249-
250-
// Returns new objects
251-
expect(chicken === result).toBe(false);
252-
expect(egg === result.lays).toBe(false);
253-
254-
// Returns new references within objects
255-
expect(chicken === result.lays[0]).toBe(false);
256-
expect(egg === result.lays[0].lays).toBe(false);
257-
258-
// Keeps circular reference
259-
expect(result.lays[0] === result).toBe(true);
238+
expect(dropUndefinedKeys(chicken)).toStrictEqual({
239+
lays: egg,
240+
weight: '1kg',
241+
});
260242
});
261243
});
262244

0 commit comments

Comments
 (0)