If you call JSON.stringify() on an object and get TypeError: Converting circular structure to JSON, the object contains a reference loop — somewhere in the data, an object points back to itself. JSON is a tree format; it cannot represent cycles.
What a circular reference looks like
const parent = { name: "parent" };
const child = { name: "child" };
parent.child = child;
child.parent = parent; // circular: child points back to parent
JSON.stringify(parent);
// TypeError: Converting circular structure to JSON
// --> starting at object with constructor 'Object'
// | property 'child' -> object with constructor 'Object'
// --- property 'parent' closes the circle
Common sources of circular references
- ORM / database model instances — when an entity has a
parentorauthorback-reference that points to the root object. - Express.js
reqandresobjects — these contain many internal circular references. Never try to stringify them directly. - DOM nodes stored in JS objects — a DOM node's
parentNodeandchildNodesform a tree with back-pointers. - Event emitter instances — many Node.js EventEmitter-based objects hold internal references that loop back.
- Linked lists and graphs — any data structure where nodes reference each other.
Fix 1 — remove the back-reference before serializing
The cleanest solution is to not include the circular property in the output at all:
const safeChild = { name: child.name }; // copy only what you need
JSON.stringify({ name: parent.name, child: safeChild }); // works
Fix 2 — use a replacer function to skip seen objects
If you do not know in advance which property is circular, use a replacer that tracks visited objects and replaces any back-reference with a placeholder:
function circularReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
};
}
JSON.stringify(parent, circularReplacer());
// '{"name":"parent","child":{"name":"child","parent":"[Circular]"}}'
Fix 3 — use a library for full circular serialization
If you need to serialize and then deserialize circular structures (not just log them), use the flatted npm package, which encodes cycles as index references:
import { stringify, parse } from 'flatted';
const json = stringify(parent); // encodes cycles
const obj = parse(json); // restores the circular structure
Note that the output format is not standard JSON — you can only parse it back with flatted.
Serializing only the data you need
In practice, the best fix for API responses is to map your internal objects to plain data transfer objects (DTOs) before serialization. This prevents accidental exposure of internal references and circular structures:
// Internal model
const user = orm.findUser(id); // has circular ORM references
// Safe DTO for the API response
const dto = {
id: user.id,
name: user.name,
email: user.email
};
res.json(dto); // never pass the ORM model directly
Why the error happens at serialization, not creation
A circular reference is perfectly legal in memory — objects pointing at each other is how linked lists, trees with parent pointers, and DOM nodes work. The problem only appears when you try to flatten that graph into text. JSON.stringify walks the object depth-first, and when it revisits a node it has already seen, it can't represent "the same object again" in a linear format, so it throws TypeError: Converting circular structure to JSON. Node.js even tells you the property path that closed the loop, which is the fastest way to find the offending back-reference. Understanding this is the key to the fix: you're not removing data the program needs, you're only deciding how to represent a shared reference when the structure is serialized.
Circular references and structuredClone
It's worth knowing that not every "deep copy" operation chokes on cycles the way JSON does. The modern structuredClone() function handles circular references natively, so if your goal is to duplicate an object in memory rather than send it over the wire, reach for that instead of the old JSON.parse(JSON.stringify(obj)) trick — the round-trip approach not only fails on cycles, it also silently drops functions, undefined, and Date objects turn into strings. Serialization for transport is a different job: there you genuinely have to break the cycle, either by removing the back-reference before stringifying or by replacing it with an ID that the receiver can re-link. When you need to confirm the result is clean, valid JSON, paste it into the JSON Validator.
Try it directly in your browser — free, no signup:
Open JSON Validator →