{}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

How do I fix "Converting circular structure to JSON"?

The error means an object references itself, directly or through a chain, so JSON.stringify() would recurse forever. Fix it by breaking the cycle before serializing: pass a replacer that tracks visited objects with a WeakSet, pick only the fields you actually need, or use structuredClone-style flattening. In Node 17+ the error message names the exact property path that closes the loop.

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));
}

Paste your JSON into the validator →

Get the exact line, column, and a fix hint in seconds — no upload, no signup.

Open JSON Validator →

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