TypeError: Converting circular structure to JSON — cause and fix
Why JSON.stringify throws 'Converting circular structure to JSON', how to detect cycles, and four battle-tested patterns for serializing circular data safely.
When you call JSON.stringify() on an object that contains a reference back to itself (directly or through a chain), JavaScript throws:
TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'child' -> object with constructor 'Object'
--- property 'parent' closes the circleWhat causes circular references
- Parent-child back-references: a child node that holds a reference to its parent, forming a cycle.
- DOM nodes: HTML elements have circular references through
parentNode,childNodes, etc. Never stringify a DOM element. - Logging frameworks / error objects:
requestandresponseobjects in Express, Fastify, and others have circular references by design. - ORM entities: Sequelize, TypeORM, and Mongoose models with bidirectional associations are often circular.
Four ways to fix it
1. Use a replacer function (standard JSON.stringify)
function safeStringify(obj) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
});
}2. Use the json-stringify-safe package
import stringify from 'json-stringify-safe'; const json = stringify(circularObj, null, 2); // replaces cycles with '[Circular ~]'
3. Use structuredClone + delete the cycle
// Remove the back-reference before stringifying const copy = structuredClone(obj); delete copy.child.parent; // break the cycle JSON.stringify(copy);
4. Map to a plain DTO before serializing
// Best for ORMs / API responses: explicitly shape the output
const dto = {
id: user.id,
name: user.name,
posts: user.posts.map(p => ({ id: p.id, title: p.title })),
// omit user.posts[n].author (which points back to user)
};
JSON.stringify(dto);Detecting cycles without stringifying
function hasCycle(obj, seen = new WeakSet()) {
if (typeof obj !== 'object' || obj === null) return false;
if (seen.has(obj)) return true;
seen.add(obj);
return Object.values(obj).some(v => hasCycle(v, seen));
}