Node.js 如何打印循环引用的对象(JSON 格式)?
JSON.stringify 遇到循环引用直接报错。解决方案:用 util.inspect 打印(自动处理循环引用),或给 JSON.stringify 传 replacer 函数剔除循环引用。本文给出三种写法。
最简单:用 util.inspect(),天生支持循环引用。JSON.stringify 遇到循环引用会直接抛 TypeError: Converting circular structure to JSON。
方法一:util.inspect(推荐)
const util = require('util');
const obj = {
prop1: 'value1',
prop2: { prop3: 'value3' }
};
obj.circularRef = obj; // 制造循环引用
console.log(util.inspect(obj, { depth: null }));
// 输出: { prop1: 'value1', prop2: { prop3: 'value3' }, circularRef: <ref *1> [Circular] }
depth: null 取消默认的嵌套层级限制(默认只展开 2 层),循环引用处显示 <ref *1> [Circular]。
其实直接 console.log(obj) 内部也走 util.inspect,同样能打印循环结构——只是层级深了会被折叠。
方法二:JSON.stringify + replacer
需要真正的 JSON 字符串(比如要写文件)时,用 replacer 函数剔除循环引用:
const seen = new WeakSet();
const json = JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]'; // 见过的对象替换为占位符
seen.add(value);
}
return value;
}, 2);
console.log(json);
方法三:现成库
safe-stable-stringify、flatted 等库处理了更多边界(BigInt、Map、性能),项目里高频遇到循环引用(如打印 ORM 实体)时直接用库。
常见问题(FAQ)
Q:循环引用从哪来的?
常见于:ORM 实体互相关联(User.posts → Post.author → User)、DOM-like 树结构(parent/children)、Express 的 req/res 对象。
Q:util.inspect 的输出能再 JSON.parse 回来吗?
不能,它是给人看的调试格式,不是合法 JSON。要可序列化用方法二/三。
Q:为什么打印 express 的 req 会炸?
req 内部有大量循环引用(req.res.req...)。别整个打印,挑需要的字段(method、url、headers)输出。