TL;DR: Cách Sửa Nhanh
Nếu bạn cần log một object mà không làm crash ứng dụng, hãy dùng một hàm replacer tùy chỉnh. Đoạn code dưới đây dùng WeakSet để theo dõi và bỏ qua những object đã được duyệt qua.
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return;
seen.add(value);
}
return value;
};
};
const jsonString = JSON.stringify(yourObject, getCircularReplacer());
Tại Sao Lỗi Này Xảy Ra
Lỗi TypeError: Converting circular structure to JSON xuất hiện khi JSON.stringify() gặp một object tự tham chiếu đến chính nó. JSON về bản chất là cấu trúc cây thuần túy. Khi bạn tạo ra một tham chiếu vòng, bạn tạo ra một đồ thị có chu trình mà thuật toán chuẩn không thể duyệt đến điểm cuối.
Hãy xem ví dụ đơn giản nhưng có vấn đề sau:
const user = { name: 'Alice' };
user.self = user; // Tạo ra vòng lặp vô tận
JSON.stringify(user); // Ném ra TypeError
Bạn sẽ gặp lỗi này thường xuyên trong Node.js khi làm việc với các instance phức tạp. Ví dụ, Express request object (req) chứa hàng trăm tham chiếu nội bộ đến server và socket. Tương tự, các model Sequelize hoặc Mongoose thường liên kết ngược lại connection cha, còn các DOM element thì liên kết đến phần tử cha, rồi phần tử cha lại liên kết ngược về con.
Các Giải Pháp Thực Tế
1. WeakSet Replacer (Tốt nhất để Logging)
Đây là cách hiệu quả nhất để in một object ra console hoặc file log. Cách này dùng WeakSet để lưu các tham chiếu. Vì WeakSet không ngăn garbage collection, nó tiết kiệm bộ nhớ hơn. Nếu replacer gặp cùng một object lần thứ hai, nó đơn giản là bỏ qua tham chiếu đó.
function safeStringify(obj) {
const cache = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (cache.has(value)) return;
cache.add(value);
}
return value;
});
}
2. Dùng Thư Viện 'flatted' (Tốt nhất để Lưu Trữ Dữ Liệu)
Đôi khi bạn cần thực sự lưu dữ liệu và khôi phục lại sau này. Nếu bạn loại bỏ các phần vòng tròn, bạn sẽ mất dữ liệu đó mãi mãi. Thư viện flatted giải quyết vấn đề này bằng cách thay thế các tham chiếu vòng bằng các định danh duy nhất trong quá trình serialize và khôi phục chúng khi parse.
Đây là package nhỏ, khoảng 1.1KB sau khi gzip. Cài đặt qua npm:
npm install flatted
Cách dùng giống hệt JSON global có sẵn:
const { stringify, parse } = require('flatted');
const circularObj = { a: 1 };
circularObj.itself = circularObj;
const serialized = stringify(circularObj);
const deserialized = parse(serialized);
console.log(deserialized.itself === deserialized); // true
3. Node.js util.inspect (Tốt nhất để Debug)
Đừng vội dùng JSON.stringify nếu bạn chỉ cần xem nội dung bên trong một biến. util.inspect có sẵn trong Node.js được tạo ra chính xác cho mục đích này. Nó tự động phát hiện các chu trình và đánh dấu chúng là [Circular] thay vì crash.
const util = require('util');
const complexObject = { /* ... */ };
// depth: null đảm bảo hiển thị tất cả các cấp lồng nhau
console.log(util.inspect(complexObject, { depth: null, colors: true }));
Cách Kiểm Tra Sau Khi Sửa
Trước khi deploy bản sửa lỗi, hãy chạy qua các kiểm tra sau:
- Kiểm tra không còn lỗi: Bọc lời gọi trong
try-catch. Nếu replacer hoạt động đúng, khối catch sẽ không còn bị kích hoạt nữa. - Kiểm tra kết quả đầu ra: Nếu bạn dùng cách
WeakSet, hãy xác nhận các key bị thiếu đúng là những key bạn muốn bỏ qua. - Kiểm tra giá trị Null: Việc loại bỏ các tham chiếu đôi khi có thể để lại các giá trị
nullcó thể làm hỏng các component frontend đang chờ dữ liệu cụ thể.
Phòng Tránh và Thực Hành Tốt
Việc debug các object phức tạp sẽ dễ hơn khi bạn thấy được cấu trúc của chúng. Các công cụ như JSON Formatter & Validator trên ToolCraft cho phép bạn dán từng phần dữ liệu để xác định chỗ nào một node con trỏ ngược lại node cha. Vì công cụ xử lý dữ liệu cục bộ ngay trên trình duyệt, các file cấu hình của bạn vẫn hoàn toàn riêng tư.
Để tránh hoàn toàn các lỗi này:
- Dùng DTO (Data Transfer Objects): Không bao giờ stringify trực tiếp một model từ database. Thay vào đó, hãy ánh xạ nó sang một object thuần chứa chỉ những trường bạn cần (ví dụ:
id,email,name). - Giữ State Phẳng: Trong React hoặc Redux, các tham chiếu vòng thường là dấu hiệu của vấn đề kiến trúc. Hãy hướng đến một state phẳng, có thể serialize được.
- Xóa Thủ Công: Nếu bạn biết một key cụ thể như
user.managerđang gây ra vòng lặp, hãy xóa nó hoặc đặt thànhundefinedtrước khi gọi stringify.

