Skip to content

Commit f36827c

Browse files
committed
rename and refactor walk
1 parent e1123a5 commit f36827c

File tree

1 file changed

+52
-33
lines changed

1 file changed

+52
-33
lines changed

packages/utils/src/normalize.ts

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { isPrimitive, isSyntheticEvent } from './is';
1+
import { Primitive } from '@sentry/types';
2+
3+
import { isNaN, isSyntheticEvent } from './is';
24
import { memoBuilder, MemoFunc } from './memo';
35
import { convertToPlainObject } from './object';
46
import { getFunctionName } from './stacktrace';
@@ -28,7 +30,7 @@ type Prototype = { constructor: (...args: unknown[]) => unknown };
2830
export function normalize(input: unknown, depth: number = +Infinity, maxProperties: number = +Infinity): any {
2931
try {
3032
// since we're at the outermost level, there is no key
31-
return walk('', input as UnknownMaybeWithToJson, depth, maxProperties);
33+
return visit('', input as UnknownMaybeWithToJson, depth, maxProperties);
3234
} catch (_oO) {
3335
return '**non-serializable**';
3436
}
@@ -52,80 +54,97 @@ export function normalizeToSize<T>(
5254
}
5355

5456
/**
55-
* Walks an object to perform a normalization on it
57+
* Visits a node to perform a normalization on it
5658
*
57-
* @param key of object that's walked in current iteration
58-
* @param value object to be walked
59+
* @param key The key corresponding to the given node
60+
* @param value The node to be visited
5961
* @param depth Optional number indicating how deep should walking be performed
60-
* @param maxProperties Optional maximum number of properties/elements included in any single object/array
62+
* @param maxProperties Optional maximum number of properties/elements included in any single object/array
6163
* @param memo Optional Memo class handling decycling
6264
*/
63-
export function walk(
65+
export function visit(
6466
key: string,
65-
value: UnknownMaybeWithToJson,
67+
value: unknown,
6668
depth: number = +Infinity,
6769
maxProperties: number = +Infinity,
6870
memo: MemoFunc = memoBuilder(),
69-
): unknown {
71+
): Primitive | unknown[] | { [key: string]: unknown } {
7072
const [memoize, unmemoize] = memo;
7173

72-
// If we reach the maximum depth, serialize whatever is left
73-
if (depth === 0) {
74-
return stringifyValue(key, value);
74+
// if the value has a `toJSON` method, bail and let it do the work
75+
const valueWithToJSON = value as unknown & { toJSON?: () => string };
76+
if (valueWithToJSON && typeof valueWithToJSON.toJSON === 'function') {
77+
try {
78+
return valueWithToJSON.toJSON();
79+
} catch (err) {
80+
return `**non-serializable** (${err})`;
81+
}
82+
}
83+
84+
// get the simple cases out of the way first
85+
if (value === null || (['number', 'boolean', 'string'].includes(typeof value) && !isNaN(value))) {
86+
return value as Primitive;
7587
}
7688

77-
// If value implements `toJSON` method, call it and return early
78-
if (value !== null && value !== undefined && typeof value.toJSON === 'function') {
79-
return value.toJSON();
89+
const stringified = stringifyValue(key, value);
90+
91+
// Anything we could potentially dig into more (objects or arrays) will have come back as `"[object XXXX]"`.
92+
// Everything else will have already been serialized, so if we don't see that pattern, we're done.
93+
if (!stringified.startsWith('[object ')) {
94+
return stringified;
8095
}
8196

82-
// `makeSerializable` provides a string representation of certain non-serializable values. For all others, it's a
83-
// pass-through. If what comes back is a primitive (either because it's been stringified or because it was primitive
84-
// all along), we're done.
85-
const serializable = stringifyValue(key, value);
86-
if (isPrimitive(serializable)) {
87-
return serializable;
97+
// we're also done if we've reached the max depth
98+
if (depth === 0) {
99+
// At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
100+
return stringified.replace('object ', '');
88101
}
89102

90-
// Create source that we will use for the next iteration. It will either be an objectified error object (`Error` type
91-
// with extracted key:value pairs) or the input itself.
103+
// Create source that we will use for the next iteration. Because not all of the properties we care about on `Error`
104+
// and `Event` instances are ennumerable, we first convert those to plain objects. (`convertToPlainObject` is a
105+
// pass-through for everything else.)
92106
const source = convertToPlainObject(value);
93107

94-
// Create an accumulator that will act as a parent for all future itterations of that branch
108+
// Create an accumulator that will act as a parent for all future iterations of this branch, and keep track of the
109+
// number of properties/entries we add to it
110+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
95111
const acc: { [key: string]: any } = Array.isArray(value) ? [] : {};
112+
let numAdded = 0;
96113

97-
// If we already walked that branch, bail out, as it's circular reference
114+
// If we've already visited this branch, bail out, as it's circular reference
98115
if (memoize(value)) {
99116
return '[Circular ~]';
100117
}
101118

102-
let propertyCount = 0;
103-
// Walk all keys of the source
119+
// visit all keys of the source
104120
for (const innerKey in source) {
105121
// Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
106122
if (!Object.prototype.hasOwnProperty.call(source, innerKey)) {
107123
continue;
108124
}
109125

110-
if (propertyCount >= maxProperties) {
126+
if (numAdded >= maxProperties) {
111127
acc[innerKey] = '[MaxProperties ~]';
112128
break;
113129
}
114130

115-
propertyCount += 1;
131+
// Recursively visit all the child nodes
132+
const innerValue: unknown = source[innerKey];
133+
acc[innerKey] = visit(innerKey, innerValue, depth - 1, maxProperties, memo);
116134

117-
// Recursively walk through all the child nodes
118-
const innerValue = source[innerKey] as UnknownMaybeWithToJson;
119-
acc[innerKey] = walk(innerKey, innerValue, depth - 1, maxProperties, memo);
135+
numAdded += 1;
120136
}
121137

122-
// Once walked through all the branches, remove the parent from memo storage
138+
// Once we've visited all the branches, remove the parent from memo storage
123139
unmemoize(value);
124140

125141
// Return accumulated values
126142
return acc;
127143
}
128144

145+
// TODO remove this in v7 (we don't use it anywhere, but it's a public method)
146+
export { visit as walk };
147+
129148
/**
130149
* Stringify the given value. Handles various known special values and types.
131150
*

0 commit comments

Comments
 (0)