Lập trình · 19/10/2026

JSON.stringify: vì sao trường undefined biến mất còn NaN thành null?

Trong console, object có trường note bằng undefined; đến JSON gửi đi, trường đó đã biến mất. Ở một vị trí khác, cùng giá trị undefined lại trở thành null trong mảng. Đây là quy tắc tuần tự hóa cần hiểu trước khi dùng JSON làm ranh giới trao đổi dữ liệu.

JSON.stringify: vì sao trường undefined biến mất còn NaN thành null?

Trong console, object có trường note bằng undefined; đến JSON gửi đi, trường đó đã biến mất. Ở một vị trí khác, cùng giá trị undefined lại trở thành null trong mảng. Đây là quy tắc tuần tự hóa cần hiểu trước khi dùng JSON làm ranh giới trao đổi dữ liệu.

Bài viết chạy các ví dụ trong bộ nhớ, không gửi request. Ý nghĩa của trường vắng mặt, null hay chuỗi rỗng phải do API cụ thể quy định; JSON.stringify không quyết định nghiệp vụ thay ứng dụng.

1. Object và mảng xử lý undefined khác nhau

const object = { name: 'Lan', note: undefined };
const array = ['Lan', undefined];

console.log(JSON.stringify(object)); // {"name":"Lan"}
console.log(JSON.stringify(array));  // ["Lan",null]
console.log(JSON.stringify(undefined) === undefined); // true

Trong object, thuộc tính có giá trị undefined bị bỏ. Trong mảng, phần tử đó được biểu diễn bằng null để giữ vị trí. Với giá trị undefined ở mức gốc, kết quả không phải chuỗi JSON "undefined" mà là giá trị JavaScript undefined.

Đừng mặc định JSON.stringify luôn trả về string. Một hàm gửi dữ liệu cần biết nó nhận loại đầu vào nào và xử lý thế nào nếu không có nội dung JSON hợp lệ để gửi.

2. NaN và Infinity không đi qua như số JSON

const payload = {
  total: NaN,
  maximum: Infinity,
  minimum: -Infinity,
  empty: null
};
console.log(JSON.stringify(payload));
// {"total":null,"maximum":null,"minimum":null,"empty":null}

Sau tuần tự hóa, bên nhận không thể nhìn riêng JSON này để biết total ban đầu là NaN hay một null có chủ đích. Nếu một phép tính lỗi không được phép thành null, hãy từ chối trước khi stringify bằng kiểm tra số hữu hạn và các ràng buộc nghiệp vụ.

Việc JSON hợp lệ không chứng minh dữ liệu đúng. Đừng dùng “stringify không ném lỗi” như tiêu chí duy nhất để cho phép gửi một bản ghi.

3. Dựng payload nhỏ với trường được chọn rõ ràng

Giả sử contract minh họa yêu cầu total là Number hữu hạn, còn note chỉ nhận string hoặc null. Null được gửi nguyên trạng; ứng dụng phải thống nhất ý nghĩa với server trước khi dùng.

function buildSummary(total, note) {
  if (typeof total !== 'number' || !Number.isFinite(total)) {
    throw new TypeError('Expected a finite total');
  }
  if (note !== null && typeof note !== 'string') {
    throw new TypeError('Expected text or null for note');
  }
  return { total, note };
}

console.log(JSON.stringify(buildSummary(0, null)));
// {"total":0,"note":null}
console.log(JSON.stringify(buildSummary(12, '')));
// {"total":12,"note":""}

Hàm không thay NaN bằng 0 và không đổi chuỗi rỗng thành null. Nó chỉ tạo hai trường đã xác nhận, tránh vô tình đưa cả object trạng thái giao diện vào payload. Tổng âm, giới hạn tối đa hay số chữ số thập phân là các kiểm tra riêng theo sản phẩm.

Nếu một API cập nhật phân biệt “không gửi trường” với “gửi null để xóa”, hãy biểu diễn hai thao tác riêng trong code và test. Không dùng một replacer đổi mọi undefined thành null khi chưa kiểm tra contract của từng trường.

4. JSON round trip không bảo toàn mọi kiểu dữ liệu

const source = {
  createdAt: new Date('2026-01-01T00:00:00Z'),
  note: undefined
};
const restored = JSON.parse(JSON.stringify(source));

console.log(typeof restored.createdAt); // string
console.log(restored.createdAt instanceof Date); // false
console.log(Object.hasOwn(restored, 'note')); // false

Date thông thường xuất ra chuỗi qua toJSON. JSON.parse mặc định không tự dựng lại Date, còn note đã mất khỏi dữ liệu tuần tự hóa nên không thể xuất hiện lại như cũ. Do đó cách stringify rồi parse không phải phép clone tổng quát giữ mọi kiểu.

Với dữ liệu có Map, Set, BigInt, tham chiếu vòng hoặc phương thức toJSON tùy biến, cần thiết kế riêng cách xuất và đọc. Đừng thêm một cơ chế chuyển đổi toàn cục chỉ để làm một ca hiện tại hết lỗi.

5. Kiểm tra cả trước và sau tuần tự hóa

  • Xác nhận trường bắt buộc còn tồn tại trong JSON cuối.
  • Phân biệt vắng mặt, null, chuỗi rỗng và số 0 theo contract.
  • Từ chối NaN và Infinity nếu trường phải là số hữu hạn.
  • Kiểm tra undefined ở mức gốc, trong object và trong mảng.
  • Kiểm tra kiểu sau JSON.parse thay vì chỉ so output console.
  • Chạy contract test với backend cho hành vi cập nhật hoặc xóa trường.

Các đoạn code đã chạy bằng Node.js local, gồm kiểm tra đầu vào của buildSummary và kết quả round trip. Chưa kiểm thử API nghiệp vụ hay cập nhật dữ liệu production. Tài liệu đối chiếu: JSON.stringify và JSON.parse.

Xem JSON là một định dạng trao đổi có giới hạn. Dựng payload có chủ đích giúp giữ rõ thông tin nào được gửi, bị bỏ hoặc đã đổi biểu diễn.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.