jsondecode.com logo

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 circle

Circular 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

ApproachOutputBest When
Custom replacerCycles become "[Circular]"Logging / debugging
safeStringify helperCycles become "[Circular]"General serialisation
Delete cycle propertyProperty is absent from outputYou control the data model
flatted npm packageLossless encoding of cyclesRound-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.

If jsondecode.com saved you time, share it with your team

Free forever. No ads. No sign-up. Help other developers find it.