JSON5 và JSONC: JSON có chú thích
Kết luận: Cả hai biến thể đều tồn tại vì cấu hình do con người viết cần chú thích và dấu phẩy cuối, nhưng cú pháp và độ phủ hệ sinh thái của chúng khác nhau, nên chọn sai sẽ làm tắc nghẽn chuỗi công cụ của bạn.

Kết luận: dùng JSONC cho chú thích trong cấu hình VS Code và TypeScript, dùng JSON5 khi bạn cần cú pháp literal ES5.1 rộng hơn như nháy đơn hay khóa không có ngoặc kép, và giữ API công khai ở JSON chuẩn.
JSON chuẩn không cho phép chú thích, nên mọi nhóm vẫn lặng lẽ viết chúng — cho tới một ngày bản build hỏng. Thay vì làm lén lút, hãy chọn một biến thể đúng đắn.
1. Hai biến thể có động cơ khác nhau
- JSONC (JSON with Comments): JSON chuẩn cộng chú thích và dấu phẩy cuối, gần như không thay đổi cú pháp nào khác. Đây là chuẩn thực tế của hệ sinh thái VS Code —
settings.json,tsconfig.jsonvà.eslintrc.jsonđều dùng nó. - JSON5: tham vọng hơn nhiều, bám trực tiếp vào literal đối tượng ES5.1 — nháy đơn, khóa không có ngoặc kép, thập lục phân,
Infinity/NaN, nối dòng bằng dấu gạch chéo ngược, và dấu+ở đầu.
Một câu để phân biệt: JSONC chỉ làm cho việc viết thoải mái hơn; JSON5 cho phép bạn viết tệp cấu hình như thể nó là JavaScript.
2. Bảng so sánh cú pháp
| Tính năng | JSON chuẩn | JSONC | JSON5 |
|---|---|---|---|
Chú thích // /* */ |
✗ | ✓ | ✓ |
| Dấu phẩy cuối | ✗ | ✓ | ✓ |
| Chuỗi dùng nháy đơn | ✗ | ✗ | ✓ |
| Khóa không có ngoặc kép | ✗ | ✗ | ✓ |
| Số thập lục phân | ✗ | ✗ | ✓ |
Dấu thập phân ở đầu/cuối .5 5. |
✗ | ✗ | ✓ |
Số bắt đầu bằng + |
✗ | ✗ | ✓ |
Infinity / NaN |
✗ | ✗ | ✓ |
Nối dòng bằng \ ở cuối dòng |
✗ | ✗ | ✓ |
| Chuỗi nhiều dòng | ✗ | ✗ | ✓ (thực hiện qua nối dòng) |
| Giá trị cấp cao nhất tùy ý | ✓ | ✓ | ✓ |
Đặc tả đầy đủ của JSON5 chỉ hơn một trang; gói json5 đã được cộng đồng duy trì từ lâu, và trình duyệt cùng Node hoạt động giống hệt nhau.
3. Phân tích: hai API, hai kiểu năng lực định vị
3.1 JSON5: phân tích toàn tài liệu đơn giản
import JSON5 from 'json5';
const config = JSON5.parse(`
{
// service port
port: 8080,
host: '0.0.0.0',
features: ['json5', 'jsonc',],
retries: 0x10,
}
`);
console.log(config.port, config.retries); // 8080 16
JSON5.parse trả về một đối tượng thuần không có thông tin vị trí — vì nó không nhắm tới tình huống chỉnh sửa, mà chỉ để "đọc cấu hình".
3.2 JSONC: cây cú pháp có vị trí
Giá trị của jsonc-parser không nằm ở phân tích mà ở chỉnh sửa. Nó cung cấp ba tầng API:
| API | Mục đích | Cách dùng điển hình |
|---|---|---|
parse |
Phân tích thành cây mang offset/length |
Tô màu cú pháp, thu gọn, điều hướng |
visit |
Duyệt theo chiều sâu; callback nhận đường dẫn nút | Kiểm tra kiểu JSONPath |
modify + applyEdits |
Tạo chỉnh sửa văn bản tối thiểu, rồi áp lên văn bản gốc | Đổi một giá trị mà giữ nguyên chú thích |
import { parseTree, findNodeAtLocation, modify, applyEdits } from 'jsonc-parser';
const text = `{
// port configuration
"server": {
"port": 8080 // 80 in production
}
}`;
const tree = parseTree(text);
const portNode = findNodeAtLocation(tree!, ['server', 'port']);
console.log(portNode?.offset, portNode?.length);
const edits = modify(text, ['server', 'port'], 3000, {
formattingOptions: { insertSpaces: true, tabSize: 2 },
});
console.log(applyEdits(text, edits));
Kết quả giữ nguyên chú thích gốc và chỉ thay ba ký tự của 8080:
{
// port configuration
"server": {
"port": 3000 // 80 in production
}
}
Đó chính là điều "chỉnh sửa cấu hình bằng biểu thức chính quy" không làm được: regex không biết phải đổi 8080 nào, và cũng không biết 8080 nằm trong một chuỗi thì phải để yên.
3.3 Thông báo lỗi phải mang vị trí
Mã lỗi của jsonc-parser là giá trị enum; những mã phổ biến là:
| Mã lỗi | Ý nghĩa |
|---|---|
InvalidSymbol |
Xuất hiện ký tự không nên có |
PropertyNameExpected |
Mong đợi một tên khóa |
ColonExpected |
Mong đợi dấu hai chấm |
CommaExpected |
Mong đợi dấu phẩy |
CloseBraceExpected |
Đối tượng chưa được đóng |
UnexpectedEndOfComment |
Chú thích chưa được đóng |
InvalidEscapeCharacter |
Escape không hợp lệ |
Khi offset được chuyển thành dòng và cột, trình biên tập có thể vẽ đường gạch đúng ngay vị trí lỗi.
4. Khi nào dùng cái nào
| Tình huống | Khuyến nghị | Lý do |
|---|---|---|
| Thân yêu cầu/phản hồi API công khai | JSON chuẩn | Các client đa dụng không hỗ trợ biến thể |
package.json, composer.json |
JSON chuẩn | Trình quản lý gói chỉ hiểu JSON chuẩn |
tsconfig.json, .vscode/settings.json |
JSONC | Mặc định của hệ sinh thái, chuỗi công cụ hỗ trợ sẵn |
| Cấu hình ứng dụng (cần chú thích) | JSONC hoặc YAML | Chú thích phải được giữ trong khi công cụ vẫn đọc và ghi được |
Cần Infinity, thập lục phân, literal phức tạp |
JSON5 | Khả năng biểu đạt mạnh nhất |
| Trao đổi dữ liệu, nhật ký, bộ nhớ đệm | Định dạng theo dòng hoặc JSON chuẩn | Cần truyền luồng và tính phổ quát |
Quy tắc ngón tay cái: nếu tệp sẽ được một "công cụ không phải con người" đọc, hãy ưu tiên JSON chuẩn; chỉ cân nhắc biến thể khi bạn chắc chắn rằng chỉ mã của chính bạn đọc nó.
5. Ba loại rủi ro mà biến thể mang lại
5.1 Yêu cầu lan sang nơi khác
Khi một công cụ khác đọc tệp cấu hình JSON5 rồi gọi JSON.parse, nó sập ngay. Triệu chứng điển hình là "chạy tốt ở máy cá nhân, hỏng trên Docker" — vì container chạy một đường đọc khác.
Biện pháp: thêm kiểm tra trong CI trên kho mã, liệt kê rõ những tệp được phép dùng biến thể; mọi tệp còn lại được kiểm tra như JSON chuẩn.
5.2 Bất ngờ quanh suy luận kiểu
JSON5 cho phép khóa không ngoặc kép và nháy đơn, nhưng không cho phép đưa vào các cấu trúc literal như ngày tháng hay biểu thức chính quy — nó vẫn là định dạng dữ liệu, không phải đánh giá JavaScript.
Người ta từng cho rằng JSON5 biểu diễn được new Date() rồi chỉ thấy phân tích lỗi. JSON5 không phải eval.
5.3 Chú thích bị âm thầm loại bỏ
Cả JSON5.stringify lẫn JSON.stringify đều không giữ chú thích. Vậy nên "đọc vào, sửa một giá trị, ghi lại" sẽ mất toàn bộ chú thích.
Khi chú thích buộc phải tồn tại, lựa chọn duy nhất là API chỉnh sửa của jsonc-parser, hoặc một thư viện YAML có hỗ trợ giữ chú thích.
6. Khuyến nghị thực tế
- [ ] Giữ một bảng "loại tệp → định dạng" trong tài liệu của nhóm để không ai tự phát minh;
- [ ] Bọc việc đọc cấu hình trong một hàm
loadConfig()duy nhất, tự quyết định bên trong dùng trình phân tích nào, để mã nghiệp vụ không bao giờ gọiJSON.parsetrực tiếp; - [ ] Khi chỉnh sửa cấu hình có chú thích bằng chương trình, dùng
modify+applyEdits; đừng bao giờ ghi lại toàn bộ tệp; - [ ] Cũng kiểm tra tệp biến thể trong CI (
jsonc-parsercho JSONC,JSON5.parsecho JSON5), nếu không lỗi cú pháp chỉ lộ ra lúc chạy; - [ ] Dùng JSON chuẩn cho giao diện công khai, khóa bộ nhớ đệm và payload của hàng đợi tin nhắn.
FAQ
Tôi nên chọn JSON5 hay JSONC?
Hãy chọn theo môi trường chạy chứ không theo số lượng cú pháp: VS Code, TypeScript và ESLint đọc JSONC mặc định, nên JSONC cho khả năng tương thích tốt nhất; chỉ dùng JSON5 khi bạn cần cú pháp literal ES5.1 rộng hơn, như nháy đơn, khóa không ngoặc kép hay Infinity. Cả hai biến thể đều không phù hợp cho phản hồi API công khai.
Tại sao trình phân tích JSON chuẩn không đọc trực tiếp được JSONC?
JSONC cho phép chú thích và dấu phẩy cuối, trong khi JSON.parse chỉ chấp nhận JSON theo RFC 8259 và báo lỗi ở cả hai trường hợp. Đọc JSONC cần trình phân tích như jsonc-parser, hoặc loại bỏ chú thích trước khi đưa văn bản cho trình phân tích chuẩn.
Chú thích trong JSONC có được giữ lại khi sửa tệp không?
Có, nhưng chỉ qua một API chỉnh sửa. modify và applyEdits của jsonc-parser thay đổi nút được nhắm tới trong khi giữ các chú thích còn lại; phân tích thành đối tượng rồi stringify sẽ làm mất mọi chú thích.