Bạn ghép hai trang dữ liệu rồi dùng new Set để loại bản ghi lặp. Mỗi trang parse JSON thành object mới, nên hai bản ghi có cùng id vẫn có thể cùng xuất hiện trong kết quả. Set không tự biết id là khóa nghiệp vụ mà ứng dụng muốn dùng.
Bài đối chiếu MDN ngày 07/11/2026 và chạy các ví dụ trên Node.js 24.18.1. Dữ liệu đều được tạo cục bộ, không gọi API. Bài không benchmark Set hay giải quyết đầy đủ việc hợp nhất bản ghi có phiên bản khác nhau.
Xác định điều gì được coi là trùng
Với object, Set so theo identity của object, tức cùng một tham chiếu. Hai object riêng có nội dung giống nhau không tự trở thành một phần tử. Với các giá trị nguyên thủy, quy tắc bằng nhau của Set lại dựa trên giá trị theo hợp đồng riêng của nó.
Trong một danh sách entity, “trùng” thường có nghĩa là cùng khóa như id. Bạn cần chọn khóa và chính sách giữ bản ghi, rồi biểu diễn chúng trong code thay vì mặc định Set sẽ so mọi trường.
1. So cùng tham chiếu với object mới
Lưu đoạn sau thành file .cjs và chạy bằng Node:
const assert = require('node:assert/strict');
const first = {id: '1', title: 'Old title'};
const sameId = {id: '1', title: 'Old title'};
const other = {id: '2', title: 'Another title'};
const uniqueReferences = [...new Set([first, first, sameId, other])];
assert.equal(uniqueReferences.length, 3);
assert.equal(uniqueReferences[0], first);
assert.equal(uniqueReferences[1], sameId);
assert.notEqual(first, sameId);
assert.equal(new Set(['1', '1', '2']).size, 2);
console.log('reference identity differs from business id');
first lặp lại cùng tham chiếu nên chỉ có một lần trong Set. sameId là một object khác, nên vẫn được giữ dù trường dữ liệu giống nhau. Các id dạng string trong phép kiểm tra cuối lại được gộp theo giá trị string.
Nếu API trả id lúc là number, lúc là string, bạn còn có vấn đề hợp đồng kiểu khóa. Đừng tự chuyển mọi thứ sang string chỉ để số phần tử giảm xuống; xác định kiểu id chuẩn ở biên dữ liệu trước.
2. Viết chính sách giữ bản ghi đầu tiên
Ví dụ độc lập sau dùng một Set chứa id, còn kết quả giữ object của lần xuất hiện đầu. Helper dành cho mảng đặc với id là string không rỗng:
const assert = require('node:assert/strict');
function uniqueById(rows) {
const seen = new Set();
return rows.filter(row => {
if (typeof row?.id !== 'string' || row.id === '') {
throw new TypeError('Each row needs a nonempty string id');
}
if (seen.has(row.id)) return false;
seen.add(row.id);
return true;
});
}
const first = {id: '1', title: 'First value'};
const later = {id: '1', title: 'Later value'};
const other = {id: '2', title: 'Other value'};
const rows = [first, later, other];
const result = uniqueById(rows);
assert.deepEqual(result, [first, other]);
assert.equal(result[0], first);
assert.equal(rows.length, 3);
assert.deepEqual(uniqueById([]), []);
assert.throws(() => uniqueById([{title: 'Missing id'}]), TypeError);
console.log('first occurrence by id verified');
Đây là chính sách giữ lần đầu, không phải chính sách chọn dữ liệu mới nhất. Nếu later chứa thông tin cập nhật cần giữ, kết quả này có thể sai yêu cầu dù đã hết id lặp. Hãy quyết định trước ưu tiên theo thời điểm, phiên bản hay vị trí trong đầu vào.
Kiểm tra dữ liệu còn được chia sẻ
Mảng kết quả là mảng mới nhưng object bên trong vẫn là các tham chiếu cũ. Nếu sửa result[0].title, nơi khác đang giữ first cũng thấy thay đổi ấy. Loại trùng không tự tạo bản sao sâu hoặc giúp dữ liệu bất biến.
Giữ ca nhiều bản ghi cùng id nhưng khác title để buộc test thể hiện chính sách chọn. Test chỉ dùng object có mọi trường giống nhau có thể khiến hai cách giữ đầu và giữ cuối trông như tương đương.
Giới hạn của khóa đơn
Nếu id chỉ duy nhất trong từng workspace, khóa phải phản ánh cả ngữ cảnh đó. Helper mẫu chưa xử lý khóa ghép. Cũng không dùng JSON.stringify toàn object như một định nghĩa mặc định cho entity trùng: các trường hiển thị có thể thay đổi mà entity vẫn là cùng một bản ghi.
Khi trả lời phỏng vấn, hãy nói rõ ba việc: Set đang chứa object hay id, khóa nghiệp vụ là gì, và bản ghi nào được giữ khi xung đột. Từ đó mới viết code và chọn ca kiểm tra; số phần tử sau khi chạy chỉ là một phần của bằng chứng.

