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.