diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index ee357967364036..d1125e67040e75 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -168,14 +168,18 @@ export function equals(one: any, other: any): boolean { * "Uncaught TypeError: Converting circular structure to JSON" */ export function safeStringify(obj: any): string { - const seen = new Set(); - return JSON.stringify(obj, (key, value) => { - if (isObject(value) || Array.isArray(value)) { - if (seen.has(value)) { + // Track only current ancestors so shared sibling references are serialized in full. + const ancestors: unknown[] = []; + return JSON.stringify(obj, function (this: unknown, key: string, value: unknown) { + if (typeof value === 'object' && value !== null) { + // `this` is the object holding `key`, pop the subtrees that are already done + while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) { + ancestors.pop(); + } + if (ancestors.includes(value)) { return '[Circular]'; - } else { - seen.add(value); } + ancestors.push(value); } if (typeof value === 'bigint') { return `[BigInt ${value.toString()}]`; diff --git a/src/vs/base/test/common/objects.test.ts b/src/vs/base/test/common/objects.test.ts index ab8018857f7649..9909e38c072c22 100644 --- a/src/vs/base/test/common/objects.test.ts +++ b/src/vs/base/test/common/objects.test.ts @@ -134,13 +134,22 @@ suite('Objects', () => { friend: '[Circular]' } }, - '[Circular]' + { + friend: { + friend: '[Circular]' + } + } ], d: [1, '[Circular]', '[Circular]'], e: '[BigInt 42]' }); }); + test('safeStringify does not treat shared references as circular', () => { + const shared = { a: 1 }; + assert.strictEqual(objects.safeStringify([shared, { x: shared, y: [shared] }]), '[{"a":1},{"x":{"a":1},"y":[{"a":1}]}]'); + }); + test('stableStringify', () => { // Stable key order regardless of insertion order const a = { b: 1, a: 2, c: { y: 1, x: 2 } };