Skip to content

Commit 48fc389

Browse files
committed
get walk out of there!
1 parent 4a1b499 commit 48fc389

File tree

1 file changed

+79
-64
lines changed

1 file changed

+79
-64
lines changed

packages/utils/src/object.ts

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

55
import { htmlTreeAsString } from './browser';
66
import { isElement, isError, isEvent, isInstanceOf, isPlainObject, isPrimitive, isSyntheticEvent } from './is';
7-
import { memoBuilder } from './memo';
7+
import { memoBuilder, MemoFunc } from './memo';
88
import { getFunctionName } from './stacktrace';
99
import { truncate } from './string';
1010

@@ -302,6 +302,81 @@ function makeSerializable<T>(value: T, key?: any): T | string {
302302

303303
type UnknownMaybeWithToJson = unknown & { toJSON?: () => string };
304304

305+
/**
306+
* Walks an object to perform a normalization on it
307+
*
308+
* @param key of object that's walked in current iteration
309+
* @param value object to be walked
310+
* @param depth Optional number indicating how deep should walking be performed
311+
* @param maxProperties Optional maximum number of properties/elements included in any single object/array
312+
* @param memo Optional Memo class handling decycling
313+
*/
314+
export function walk(
315+
key: string,
316+
value: UnknownMaybeWithToJson,
317+
depth: number = +Infinity,
318+
maxProperties: number = +Infinity,
319+
memo: MemoFunc = memoBuilder(),
320+
): unknown {
321+
const [memoize, unmemoize] = memo;
322+
323+
// If we reach the maximum depth, serialize whatever is left
324+
if (depth === 0) {
325+
return serializeValue(value);
326+
}
327+
328+
// If value implements `toJSON` method, call it and return early
329+
if (value !== null && value !== undefined && typeof value.toJSON === 'function') {
330+
return value.toJSON();
331+
}
332+
333+
// `makeSerializable` provides a string representation of certain non-serializable values. For all others, it's a
334+
// pass-through. If what comes back is a primitive (either because it's been stringified or because it was primitive
335+
// all along), we're done.
336+
const serializable = makeSerializable(value, key);
337+
if (isPrimitive(serializable)) {
338+
return serializable;
339+
}
340+
341+
// Create source that we will use for the next iteration. It will either be an objectified error object (`Error` type
342+
// with extracted key:value pairs) or the input itself.
343+
const source = getWalkSource(value);
344+
345+
// Create an accumulator that will act as a parent for all future itterations of that branch
346+
const acc: { [key: string]: any } = Array.isArray(value) ? [] : {};
347+
348+
// If we already walked that branch, bail out, as it's circular reference
349+
if (memoize(value)) {
350+
return '[Circular ~]';
351+
}
352+
353+
let propertyCount = 0;
354+
// Walk all keys of the source
355+
for (const innerKey in source) {
356+
// Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
357+
if (!Object.prototype.hasOwnProperty.call(source, innerKey)) {
358+
continue;
359+
}
360+
361+
if (propertyCount >= maxProperties) {
362+
acc[innerKey] = '[MaxProperties ~]';
363+
break;
364+
}
365+
366+
propertyCount += 1;
367+
368+
// Recursively walk through all the child nodes
369+
const innerValue: UnknownMaybeWithToJson = source[innerKey];
370+
acc[innerKey] = walk(innerKey, innerValue, depth - 1, maxProperties, memo);
371+
}
372+
373+
// Once walked through all the branches, remove the parent from memo storage
374+
unmemoize(value);
375+
376+
// Return accumulated values
377+
return acc;
378+
}
379+
305380
/**
306381
* Recursively normalizes the given object.
307382
*
@@ -317,74 +392,14 @@ type UnknownMaybeWithToJson = unknown & { toJSON?: () => string };
317392
*
318393
* @param input The object to be normalized.
319394
* @param depth The max depth to which to normalize the object. (Anything deeper stringified whole.)
320-
* @param maxProperties The max number of elements or properties to be included in any single array or
395+
* @param maxProperties The max number of elements or properties to be included in any single array or
321396
* object in the normallized output..
322-
* @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normaliztion.
397+
* @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization.
323398
*/
324399
export function normalize(input: unknown, depth: number = +Infinity, maxProperties: number = +Infinity): any {
325-
const [memoize, unmemoize] = memoBuilder();
326-
327-
function walk(key: string, value: UnknownMaybeToJson, depth: number = +Infinity): unknown {
328-
// If we reach the maximum depth, serialize whatever is left
329-
if (depth === 0) {
330-
return serializeValue(value);
331-
}
332-
333-
// If value implements `toJSON` method, call it and return early
334-
if (value !== null && value !== undefined && typeof value.toJSON === 'function') {
335-
return value.toJSON();
336-
}
337-
338-
// `makeSerializable` provides a string representation of certain non-serializable values. For all others, it's a
339-
// pass-through. If what comes back is a primitive (either because it's been stringified or because it was primitive
340-
// all along), we're done.
341-
const serializable = makeSerializable(value, key);
342-
if (isPrimitive(serializable)) {
343-
return serializable;
344-
}
345-
346-
// Create source that we will use for the next iteration. It will either be an objectified error object (`Error` type
347-
// with extracted key:value pairs) or the input itself.
348-
const source = getWalkSource(value);
349-
350-
// Create an accumulator that will act as a parent for all future itterations of that branch
351-
const acc: { [key: string]: any } = Array.isArray(value) ? [] : {};
352-
353-
// If we already walked that branch, bail out, as it's circular reference
354-
if (memoize(value)) {
355-
return '[Circular ~]';
356-
}
357-
358-
let propertyCount = 0;
359-
// Walk all keys of the source
360-
for (const innerKey in source) {
361-
// Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
362-
if (!Object.prototype.hasOwnProperty.call(source, innerKey)) {
363-
continue;
364-
}
365-
366-
if (propertyCount >= maxProperties) {
367-
acc[innerKey] = '[MaxProperties ~]';
368-
break;
369-
}
370-
371-
propertyCount += 1;
372-
373-
// Recursively walk through all the child nodes
374-
const innerValue: UnknownMaybeToJson = source[innerKey];
375-
acc[innerKey] = walk(innerKey, innerValue, depth - 1);
376-
}
377-
378-
// Once walked through all the branches, remove the parent from memo storage
379-
unmemoize(value);
380-
381-
// Return accumulated values
382-
return acc;
383-
}
384-
385400
try {
386401
// since we're at the outermost level, there is no key
387-
return walk('', input as UnknownMaybeToJson, depth);
402+
return walk('', input as UnknownMaybeWithToJson, depth, maxProperties);
388403
} catch (_oO) {
389404
return '**non-serializable**';
390405
}

0 commit comments

Comments
 (0)