From 87f4980e7e23d34efc56e74b6ba27824abfe1a51 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 7 Mar 2022 19:54:19 +0000 Subject: [PATCH] get `walk` out of there! --- packages/utils/src/object.ts | 143 +++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 64 deletions(-) diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index 3c33691806fd..ab2351d2311e 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -4,7 +4,7 @@ import { ExtendedError, WrappedFunction } from '@sentry/types'; import { htmlTreeAsString } from './browser'; import { isElement, isError, isEvent, isInstanceOf, isPlainObject, isPrimitive, isSyntheticEvent } from './is'; -import { memoBuilder } from './memo'; +import { memoBuilder, MemoFunc } from './memo'; import { getFunctionName } from './stacktrace'; import { truncate } from './string'; @@ -302,6 +302,81 @@ function makeSerializable(value: T, key?: any): T | string { type UnknownMaybeWithToJson = unknown & { toJSON?: () => string }; +/** + * Walks an object to perform a normalization on it + * + * @param key of object that's walked in current iteration + * @param value object to be walked + * @param depth Optional number indicating how deep should walking be performed + * @param maxProperties Optional maximum number of properties/elements included in any single object/array + * @param memo Optional Memo class handling decycling + */ +export function walk( + key: string, + value: UnknownMaybeWithToJson, + depth: number = +Infinity, + maxProperties: number = +Infinity, + memo: MemoFunc = memoBuilder(), +): unknown { + const [memoize, unmemoize] = memo; + + // If we reach the maximum depth, serialize whatever is left + if (depth === 0) { + return serializeValue(value); + } + + // If value implements `toJSON` method, call it and return early + if (value !== null && value !== undefined && typeof value.toJSON === 'function') { + return value.toJSON(); + } + + // `makeSerializable` provides a string representation of certain non-serializable values. For all others, it's a + // pass-through. If what comes back is a primitive (either because it's been stringified or because it was primitive + // all along), we're done. + const serializable = makeSerializable(value, key); + if (isPrimitive(serializable)) { + return serializable; + } + + // Create source that we will use for the next iteration. It will either be an objectified error object (`Error` type + // with extracted key:value pairs) or the input itself. + const source = getWalkSource(value); + + // Create an accumulator that will act as a parent for all future itterations of that branch + const acc: { [key: string]: any } = Array.isArray(value) ? [] : {}; + + // If we already walked that branch, bail out, as it's circular reference + if (memoize(value)) { + return '[Circular ~]'; + } + + let propertyCount = 0; + // Walk all keys of the source + for (const innerKey in source) { + // Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration. + if (!Object.prototype.hasOwnProperty.call(source, innerKey)) { + continue; + } + + if (propertyCount >= maxProperties) { + acc[innerKey] = '[MaxProperties ~]'; + break; + } + + propertyCount += 1; + + // Recursively walk through all the child nodes + const innerValue: UnknownMaybeWithToJson = source[innerKey]; + acc[innerKey] = walk(innerKey, innerValue, depth - 1); + } + + // Once walked through all the branches, remove the parent from memo storage + unmemoize(value); + + // Return accumulated values + return acc; +} + /** * Recursively normalizes the given object. * @@ -317,74 +392,14 @@ type UnknownMaybeWithToJson = unknown & { toJSON?: () => string }; * * @param input The object to be normalized. * @param depth The max depth to which to normalize the object. (Anything deeper stringified whole.) - * @param maxProperties The max number of elements or properties to be included in any single array or + * @param maxProperties The max number of elements or properties to be included in any single array or * object in the normallized output.. - * @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normaliztion. + * @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization. */ export function normalize(input: unknown, depth: number = +Infinity, maxProperties: number = +Infinity): any { - const [memoize, unmemoize] = memoBuilder(); - - function walk(key: string, value: UnknownMaybeToJson, depth: number = +Infinity): unknown { - // If we reach the maximum depth, serialize whatever is left - if (depth === 0) { - return serializeValue(value); - } - - // If value implements `toJSON` method, call it and return early - if (value !== null && value !== undefined && typeof value.toJSON === 'function') { - return value.toJSON(); - } - - // `makeSerializable` provides a string representation of certain non-serializable values. For all others, it's a - // pass-through. If what comes back is a primitive (either because it's been stringified or because it was primitive - // all along), we're done. - const serializable = makeSerializable(value, key); - if (isPrimitive(serializable)) { - return serializable; - } - - // Create source that we will use for the next iteration. It will either be an objectified error object (`Error` type - // with extracted key:value pairs) or the input itself. - const source = getWalkSource(value); - - // Create an accumulator that will act as a parent for all future itterations of that branch - const acc: { [key: string]: any } = Array.isArray(value) ? [] : {}; - - // If we already walked that branch, bail out, as it's circular reference - if (memoize(value)) { - return '[Circular ~]'; - } - - let propertyCount = 0; - // Walk all keys of the source - for (const innerKey in source) { - // Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration. - if (!Object.prototype.hasOwnProperty.call(source, innerKey)) { - continue; - } - - if (propertyCount >= maxProperties) { - acc[innerKey] = '[MaxProperties ~]'; - break; - } - - propertyCount += 1; - - // Recursively walk through all the child nodes - const innerValue: UnknownMaybeToJson = source[innerKey]; - acc[innerKey] = walk(innerKey, innerValue, depth - 1); - } - - // Once walked through all the branches, remove the parent from memo storage - unmemoize(value); - - // Return accumulated values - return acc; - } - try { // since we're at the outermost level, there is no key - return walk('', input as UnknownMaybeToJson, depth); + return walk('', input as UnknownMaybeWithToJson, depth, maxProperties); } catch (_oO) { return '**non-serializable**'; }