Print objects with circular references in Node.js
Use util.inspect for debugging, replace cycles when ordinary JSON is required, or choose a paired serializer when reference identity matters.
Use util.inspect() to inspect a circular object. Its output is readable debugging text, not JSON. If a downstream system requires JSON, decide whether replacing cycles is acceptable or whether you need a format that preserves references.
Inspect the object directly
const { inspect } = require('node:util');
const obj = { prop1: 'value1', prop2: { prop3: 'value3' } };
obj.circularRef = obj;
console.log(inspect(obj, { depth: null }));
Node marks the repeated reference with labels such as [Circular *1]. Exact formatting can change across releases. depth: null removes the nesting-depth limit; other output limits still apply. console.log(obj) also handles circular objects, but its default inspection depth may hide nested details.
Do not pass inspection output to JSON.parse(). It is not a serialization format.
Produce JSON with cycle markers
A single WeakSet of every object seen will also flag shared objects that are not cycles. Track the current ancestor path if the distinction matters:
function circularReplacer() {
const ancestors = [];
return function (key, value) {
if (value === null || typeof value !== 'object') return value;
while (ancestors.length && ancestors[ancestors.length - 1] !== this) {
ancestors.pop();
}
if (ancestors.includes(value)) return '[Circular]';
ancestors.push(value);
return value;
};
}
const child = { value: 1 };
const data = { first: child, second: child };
data.self = data;
console.log(JSON.stringify(data, circularReplacer(), 2));
This preserves both occurrences of child as ordinary JSON objects and replaces the actual cycle with a string. Create a fresh replacer for each serialization. The result loses reference identity and cannot reconstruct the original graph. Normal JSON limitations still apply, including errors for unhandled BigInt values and the absence of built-in Map serialization.
Choose a library for the required format
safe-stable-stringify provides configurable handling of cycles and BigInt, with deterministic key ordering. Review its policies rather than assuming its output is identical to JSON.stringify().
flatted encodes references and requires its matching stringify and parse functions for a round trip. Its serialized text can be syntactically valid JSON while still requiring the library's decoder to recover the graph. Neither choice automatically defines suitable serialization for every application type.
Cycles commonly appear in ORM relationships, parent/child structures, and HTTP request/response objects. For request logs, select a small set of useful fields and redact credentials. Dumping an entire request can disclose tokens, cookies, or bodies even when the serializer handles cycles correctly.
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan