Fix: JSON Circular Reference (Converting Circular Structure to JSON)
If you have seen TypeError: Converting circular structure to JSON, your object contains a property that eventually references itself. Here are three ways to fix it.
What Is a Circular Reference?
A circular reference is created when an object's property chain loops back to itself. The simplest example:
const obj = { name: "Alice" };
obj.self = obj; // circular reference!
JSON.stringify(obj);
// TypeError: Converting circular structure to JSON
// --> starting at object with constructor 'Object'
// --- property 'self' closes the circleCircular references appear frequently with DOM nodes, Express req/res objects, Mongoose documents with populated references, and complex class instances that hold parent references.
Solution 1: Custom Replacer Function
The cleanest approach for most cases. Pass a replacer to JSON.stringify() that tracks visited objects and replaces cycles with a sentinel string:
function getCircularReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
}
return value;
};
}
const obj = { name: "Alice" };
obj.self = obj;
JSON.stringify(obj, getCircularReplacer(), 2);
// {
// "name": "Alice",
// "self": "[Circular]"
// }Solution 2: Cycle-Safe Stringify Helper
If you need a drop-in replacement for JSON.stringify() that never throws on cycles, wrap it in a reusable helper:
function safeStringify(value, indent = 2) {
const seen = new WeakSet();
return JSON.stringify(
value,
(key, val) => {
if (typeof val === "object" && val !== null) {
if (seen.has(val)) return "[Circular]";
seen.add(val);
}
return val;
},
indent
);
}
// Works even on deeply nested cycles
const tree = { id: 1, children: [] };
tree.children.push({ id: 2, parent: tree }); // cycle via parent
console.log(safeStringify(tree));Solution 3: Break the Cycle Before Serialising
If you control the data structure, the cleanest fix is to never create the cycle in the first place. Use structuredClone() to deep-clone the object and then manually delete the circular property:
const original = { name: "Alice", parent: null };
original.self = original; // circular
// structuredClone handles cycles when cloning
const clone = structuredClone(original);
delete clone.self; // remove the reference you don't need in JSON
JSON.stringify(clone, null, 2);
// { "name": "Alice", "parent": null }Comparison of Approaches
| Approach | Output | Best When |
|---|---|---|
| Custom replacer | Cycles become "[Circular]" | Logging / debugging |
| safeStringify helper | Cycles become "[Circular]" | General serialisation |
| Delete cycle property | Property is absent from output | You control the data model |
| flatted npm package | Lossless encoding of cycles | Round-trip serialisation needed |
Frequently Asked Questions
What is a circular reference in JavaScript?▾
A circular reference occurs when an object's property chain eventually points back to the same object, creating an infinite loop. For example: const a = {}; a.self = a; creates a circular reference because a.self === a.
Why does JSON.stringify() throw on circular references?▾
The JSON format is a tree structure — it has no way to represent shared or cyclic references. When JSON.stringify() encounters an object it has already visited in the current path, it throws a TypeError to prevent infinite recursion.
Does structuredClone remove circular references?▾
No. structuredClone() correctly handles circular references when cloning (it preserves them in the clone), but you still cannot JSON.stringify() the result because the circular structure remains. Use a custom replacer to strip cycles before serializing.
What does the replacer function do in JSON.stringify()?▾
The second argument to JSON.stringify() is a replacer — a function called for every value being serialized. You can use it to intercept circular references and replace them with a placeholder string like '[Circular]' instead of throwing.
Are there npm packages that handle circular JSON?▾
Yes. Popular options include 'flatted' (encodes cycles using a reference index), 'json-stringify-safe' (replaces cycles with '[Circular]'), and 'circular-json' (deprecated — prefer flatted). For most use cases a small inline replacer is sufficient.
Related Error Guides
If jsondecode.com saved you time, share it with your team
Free forever. No ads. No sign-up. Help other developers find it.