{}JSON FYI

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.

·6 min read

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 circle

What 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: request and response objects 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));
}

Frequently asked questions

Does JSON.parse ever produce circular references?+

No — JSON is a text format with no way to express references. JSON.parse always produces a plain acyclic object graph.

Why do Express req/res objects cause this error?+

Express request and response objects reference the app, the socket, and each other in complex ways. Never pass req or res directly to JSON.stringify. Always extract the specific fields you need.

Does structuredClone handle circular references?+

Yes — structuredClone correctly copies circular objects. But the result is still circular, so you'd still need to break the cycle before calling JSON.stringify on the clone.

Related tools & guides