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 函数剔除循环引用:
function makeReplacer() {
const ancestors = [];
return function (key, value) {
if (typeof value !== 'object' || value === null) return value;
while (ancestors.length && ancestors[ancestors.length - 1] !== this) ancestors.pop();
if (ancestors.includes(value)) return '[Circular]';
ancestors.push(value);
return value;
};
}
const json = JSON.stringify(obj, makeReplacer(), 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。方法二输出合法 JSON,但占位符丢失循环关系;flatted 需配套 parse 才能恢复其支持的引用结构,不可与 JSON.parse 任意混用。
Q:为什么打印 express 的 req 会炸?
req 内部有大量循环引用(req.res.req...)。别整个打印,选择 method、脱敏后的路径和必要白名单字段;URL 查询及 Authorization/Cookie 等头可能含密钥,不应整包输出。