From d510466402fceb69162f9595315601d68dfd6f14 Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Wed, 26 Jan 2022 09:50:19 -0800 Subject: [PATCH 1/4] stop using JSON parse and stringify in normalization --- packages/utils/src/object.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index f359a891c900..bef1cc22abe4 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -372,7 +372,8 @@ export function walk(key: string, value: any, depth: number = +Infinity, memo: M // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function normalize(input: any, depth?: number): any { try { - return JSON.parse(JSON.stringify(input, (key: string, value: any) => walk(key, value, depth))); + // since we're at the outermost level, there is no key + return walk('', input, depth); } catch (_oO) { return '**non-serializable**'; } From fb3bf7ada42191bfaeb0e3f63c60e9bc084d5b9d Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Wed, 26 Jan 2022 12:40:32 -0800 Subject: [PATCH 2/4] s/normalizeValue/makeSerializable and update docstring --- packages/utils/src/object.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index bef1cc22abe4..89c090fd7e12 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -231,20 +231,20 @@ function serializeValue(value: any): any { return '[Array]'; } - const normalized = normalizeValue(value); - return isPrimitive(normalized) ? normalized : type; + // `makeSerializable` provides a string representation of certain non-serializable values. For all others, it's a + // pass-through. + const serializable = makeSerializable(value); + return isPrimitive(serializable) ? serializable : type; } /** - * normalizeValue() + * makeSerializable() * - * Takes unserializable input and make it serializable friendly + * Takes unserializable input and make it serializer-friendly. * - * - translates undefined/NaN values to "[undefined]"/"[NaN]" respectively, - * - serializes Error objects - * - filter global objects + * Handles globals, functions, `undefined`, `NaN`, and other non-serializable values. */ -function normalizeValue(value: T, key?: any): T | string { +function makeSerializable(value: T, key?: any): T | string { if (key === 'domain' && value && typeof value === 'object' && (value as unknown as { _events: any })._events) { return '[Domain]'; } @@ -322,10 +322,12 @@ export function walk(key: string, value: any, depth: number = +Infinity, memo: M } /* eslint-enable @typescript-eslint/no-unsafe-member-access */ - // If normalized value is a primitive, there are no branches left to walk, so bail out - const normalized = normalizeValue(value, key); - if (isPrimitive(normalized)) { - return normalized; + // `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 From 8531d2bc36f595c0fd796e6f0c14f9d5b79913ba Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Wed, 26 Jan 2022 12:41:48 -0800 Subject: [PATCH 3/4] split out value and move typecast for readability --- packages/utils/src/object.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index 89c090fd7e12..8d5662738597 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -335,7 +335,7 @@ export function walk(key: string, value: any, depth: number = +Infinity, memo: M const source = getWalkSource(value); // Create an accumulator that will act as a parent for all future itterations of that branch - const acc = Array.isArray(value) ? [] : {}; + const acc: { [key: string]: any } = Array.isArray(value) ? [] : {}; // If we already walked that branch, bail out, as it's circular reference if (memo[0](value)) { @@ -349,7 +349,8 @@ export function walk(key: string, value: any, depth: number = +Infinity, memo: M continue; } // Recursively walk through all the child nodes - (acc as { [key: string]: any })[innerKey] = walk(innerKey, source[innerKey], depth - 1, memo); + const innerValue: any = source[innerKey]; + acc[innerKey] = walk(innerKey, innerValue, depth - 1, memo); } // Once walked through all the branches, remove the parent from memo storage From e2c27d6a217b05b7c3f071f6c8cbfb0d6ee5a94c Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Wed, 26 Jan 2022 12:52:11 -0800 Subject: [PATCH 4/4] refer to memoization methods by name rather than number --- packages/utils/src/memo.ts | 7 ++++++- packages/utils/src/object.ts | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/utils/src/memo.ts b/packages/utils/src/memo.ts index 735c9446e16b..d76f60579bc4 100644 --- a/packages/utils/src/memo.ts +++ b/packages/utils/src/memo.ts @@ -1,7 +1,12 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-explicit-any */ -export type MemoFunc = [(obj: any) => boolean, (obj: any) => void]; +export type MemoFunc = [ + // memoize + (obj: any) => boolean, + // unmemoize + (obj: any) => void, +]; /** * Helper to decycle json objects diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index 8d5662738597..568d1b34f99b 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -310,6 +310,8 @@ function makeSerializable(value: T, key?: any): T | string { */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function walk(key: string, value: any, depth: number = +Infinity, memo: MemoFunc = memoBuilder()): any { + const [memoize, unmemoize] = memo; + // If we reach the maximum depth, serialize whatever is left if (depth === 0) { return serializeValue(value); @@ -338,7 +340,7 @@ export function walk(key: string, value: any, depth: number = +Infinity, memo: M const acc: { [key: string]: any } = Array.isArray(value) ? [] : {}; // If we already walked that branch, bail out, as it's circular reference - if (memo[0](value)) { + if (memoize(value)) { return '[Circular ~]'; } @@ -354,7 +356,7 @@ export function walk(key: string, value: any, depth: number = +Infinity, memo: M } // Once walked through all the branches, remove the parent from memo storage - memo[1](value); + unmemoize(value); // Return accumulated values return acc;