diff --git a/packages/nodes-base/nodes/Code/utils.ts b/packages/nodes-base/nodes/Code/utils.ts index d4adfde155..67726f1485 100644 --- a/packages/nodes-base/nodes/Code/utils.ts +++ b/packages/nodes-base/nodes/Code/utils.ts @@ -12,9 +12,7 @@ function isTraversable(maybe: unknown): maybe is IDataObject { * Stringify any non-standard JS objects (e.g. `Date`, `RegExp`) inside output items at any depth. */ export function standardizeOutput(output: IDataObject) { - const knownObjects = new WeakSet(); - - function standardizeOutputRecursive(obj: IDataObject): IDataObject { + function standardizeOutputRecursive(obj: IDataObject, knownObjects = new WeakSet()): IDataObject { for (const [key, value] of Object.entries(obj)) { if (!isTraversable(value)) continue; @@ -29,7 +27,7 @@ export function standardizeOutput(output: IDataObject) { obj[key] = value.constructor.name !== 'Object' ? JSON.stringify(value) // Date, RegExp, etc. - : standardizeOutputRecursive(value); + : standardizeOutputRecursive(value, knownObjects); } return obj; } diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index 6f7a9fdad1..b1ada63be1 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -64,27 +64,24 @@ export const jsonParse = (jsonString: string, options?: JSONParseOptions): type JSONStringifyOptions = { replaceCircularRefs?: boolean; - circularRefReplacement?: string; }; -const getReplaceCircularReferencesFn = (options: JSONStringifyOptions) => { - const knownObjects = new WeakSet(); - return (key: any, value: any) => { - if (typeof value === 'object' && value !== null) { - if (knownObjects.has(value)) { - return options?.circularRefReplacement ?? '[Circular Reference]'; - } - knownObjects.add(value); +const replaceCircularReferences = (value: T, knownObjects = new WeakSet()): T => { + if (value && typeof value === 'object') { + if (knownObjects.has(value)) return '[Circular Reference]' as T; + knownObjects.add(value); + const copy = (Array.isArray(value) ? [] : {}) as T; + for (const key in value) { + copy[key] = replaceCircularReferences(value[key], knownObjects); } - return value; - }; + knownObjects.delete(value); + return copy; + } + return value; }; export const jsonStringify = (obj: unknown, options: JSONStringifyOptions = {}): string => { - const replacer = options?.replaceCircularRefs - ? getReplaceCircularReferencesFn(options) - : undefined; - return JSON.stringify(obj, replacer); + return JSON.stringify(options?.replaceCircularRefs ? replaceCircularReferences(obj) : obj); }; export const sleep = async (ms: number): Promise => diff --git a/packages/workflow/test/utils.test.ts b/packages/workflow/test/utils.test.ts index 0cc34f4ef4..fb944ec62a 100644 --- a/packages/workflow/test/utils.test.ts +++ b/packages/workflow/test/utils.test.ts @@ -30,6 +30,14 @@ describe('jsonStringify', () => { '{"a":1,"b":2,"c":"[Circular Reference]"}', ); }); + + it('should not detect duplicates as circular references', () => { + const y = { z: 5 }; + const x = [y, y, { y }]; + expect(jsonStringify(x, { replaceCircularRefs: true })).toEqual( + '[{"z":5},{"z":5},{"y":{"z":5}}]', + ); + }); }); describe('deepCopy', () => {