Skip to content

Commit

Permalink
Fix a false-positive for circular object
Browse files Browse the repository at this point in the history
Fixes #2
  • Loading branch information
sindresorhus committed Apr 10, 2022
1 parent 587d7f8 commit 566526e
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 16 deletions.
11 changes: 3 additions & 8 deletions index.js
@@ -1,24 +1,19 @@
const makeCircularReplacer = () => {
const seen = new WeakSet();
const seen = new WeakMap();

return (key, value) => {
if (value !== null && typeof value === 'object') {
if (seen.has(value)) {
if (seen.has(value) && seen.get(value) !== key) {
return '[Circular]';
}

seen.add(value);
seen.set(value, key);
}

return value;
};
};

export default function safeStringify(object, {indentation} = {}) {
// Without this, the replacer would replace duplicate objects in a top-level array.
if (Array.isArray(object)) {
object = object.map(element => JSON.parse(JSON.stringify(element, makeCircularReplacer())));
}

return JSON.stringify(object, makeCircularReplacer(), indentation);
}
2 changes: 1 addition & 1 deletion readme.md
Expand Up @@ -55,4 +55,4 @@ Set it to `'\t'` for tab indentation or the number of spaces you want.

### Why another safe stringify package?

The existing ones either did too much, did it incorrectly, or used inefficient code (not using `WeakSet`). For example, many packages incorrectly replaced all duplicate objects, not just circular references.
The existing ones either did too much, did it incorrectly, or used inefficient code (not using `WeakMap`). For example, many packages incorrectly replaced all duplicate objects, not just circular references, and did not handle circular arrays.
30 changes: 26 additions & 4 deletions test.js
@@ -1,6 +1,10 @@
import test from 'ava';
import safeStringify from './index.js';

const options = {
indentation: '\t',
};

test('main', t => {
const fixture = {
a: true,
Expand All @@ -9,7 +13,7 @@ test('main', t => {
},
};

t.is(safeStringify(fixture, {indentation: '\t'}), JSON.stringify(fixture, undefined, '\t'));
t.is(safeStringify(fixture, options), JSON.stringify(fixture, undefined, '\t'));
});

test('circular object', t => {
Expand All @@ -25,15 +29,33 @@ test('circular object', t => {
e: fixture.c,
};

t.snapshot(safeStringify(fixture));
t.snapshot(safeStringify(fixture, options));
});

test('circular array', t => {
const fixture = [1];

fixture.push(fixture, fixture);

t.snapshot(safeStringify(fixture, options));
});

test('multiple circular objects in array', t => {
const fixture = {
a: true,
};

fixture.b = fixture;

t.snapshot(safeStringify([fixture, fixture], options));
});

test('multiple circular objects', t => {
test('multiple circular objects in object', t => {
const fixture = {
a: true,
};

fixture.b = fixture;

t.snapshot(safeStringify([fixture, fixture]));
t.snapshot(safeStringify({x: fixture, y: fixture}, options));
});
44 changes: 41 additions & 3 deletions test.js.md
Expand Up @@ -8,10 +8,48 @@ Generated by [AVA](https://avajs.dev).

> Snapshot 1
'{"a":true,"b":"[Circular]","c":["[Circular]","[Circular]"],"d":{"e":"[Circular]"}}'
`{␊
"a": true,␊
"b": "[Circular]",␊
"c": [␊
"[Circular]",␊
"[Circular]"␊
],␊
"d": {␊
"e": "[Circular]"␊
}␊
}`

## circular array

## multiple circular objects
> Snapshot 1
`[␊
1,␊
"[Circular]",␊
"[Circular]"␊
]`

## multiple circular objects in array

> Snapshot 1
`[␊
{␊
"a": true,␊
"b": "[Circular]"␊
},␊
"[Circular]"␊
]`

## multiple circular objects in object

> Snapshot 1
'[{"a":true,"b":"[Circular]"},{"a":true,"b":"[Circular]"}]'
`{␊
"x": {␊
"a": true,␊
"b": "[Circular]"␊
},␊
"y": "[Circular]"␊
}`
Binary file modified test.js.snap
Binary file not shown.

0 comments on commit 566526e

Please sign in to comment.