jsonerrorsjavascriptnodedebugging

Converting Circular Structure to JSON — Fixed

·6 min read

What Is a Circular Structure?

A circular reference exists when an object has a property that points back to itself — directly or through a chain of other objects. JSON.stringify() cannot handle this and throws:

TypeError: Converting circular structure to JSON

How Circular References Are Created

Direct self-reference:

javascript
const obj = { name: 'Ravi' };
obj.self = obj; // obj.self points back to obj
JSON.stringify(obj); // TypeError

Parent-child cycle (common in DOM nodes, linked lists, trees with parent pointers):

javascript
const parent = { name: 'Parent', children: [] };
const child  = { name: 'Child', parent };
parent.children.push(child);
// child.parent.children[0].parent.children[0]... infinite loop
JSON.stringify(parent); // TypeError

Node.js objects — Error objects, Request objects, and many built-in Node.js objects have circular references internally:

javascript
const err = new Error('something failed');
JSON.stringify(err); // TypeError — Error has circular refs

Fix 1: Custom Replacer to Skip Circular Keys

javascript
function safeStringify(obj, indent) {
  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;
  }, indent);
}

const result = safeStringify(obj, 2);
// Circular references become the string "[Circular]"

Fix 2: Use the json-stringify-safe Package

bash
npm install json-stringify-safe
javascript
const stringify = require('json-stringify-safe');
const json = stringify(obj, null, 2);

Fix 3: Use flatted for Round-Trip Serialization

flatted encodes circular references in a way that can be decoded back:

bash
npm install flatted
javascript
import { stringify, parse } from 'flatted';

const json   = stringify(obj);   // handles circular refs
const parsed = parse(json);      // restores the structure

This is the best option when you need to serialize and deserialize the object while preserving the circular references.

Fix 4: Remove the Circular Reference Before Serializing

If you control the data structure, remove the back-reference before serializing:

javascript
const parent = { name: 'Parent', children: [] };
const child  = { name: 'Child' }; // no parent pointer
parent.children.push(child);

JSON.stringify(parent); // works fine

Or create a clean copy with only the fields you need:

javascript
const clean = {
  id: user.id,
  name: user.name,
  email: user.email,
  // omit: user.account (circular back-ref to user)
};
JSON.stringify(clean);

Fix 5: Serializing Error Objects

Error objects do not serialize well by default — their properties are not enumerable:

javascript
const err = new Error('something failed');
JSON.stringify(err); // {} — empty object, or circular error

// Instead, build a plain object:
JSON.stringify({
  message: err.message,
  name:    err.name,
  stack:   err.stack,
});

Detecting Circular References Without Serializing

javascript
function hasCircular(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 => hasCircular(v, seen));
}

console.log(hasCircular(obj)); // true or false

Common Culprits in Node.js Projects

  • Express req and res objects (huge circular ref graphs)
  • Mongoose/Sequelize model instances (virtual properties, populated refs)
  • DOM elements and React fiber objects
  • Linked lists, trees with parent pointers
  • Custom event emitters with back-references

Try JSON Formatter

Inspect your JSON structure to spot circular references before serializing.