Skip to content
Back to Blog
format-comparisons

YAML vs JSON vs TOML: Lựa chọn định dạng file cấu hình

2026-05-17 9 phút đọc

Tại sao việc lựa chọn định dạng cấu hình lại thực sự quan trọng

Chọn sai định dạng cấu hình, và bạn sẽ mất hàng giờ để gỡ lỗi một dấu phẩy bị thiếu. Bạn sẽ phải vật lộn với một trình phân tích cú pháp tự động bỏ qua một key. Bạn sẽ phải giải thích lại các quy tắc thụt lề khó hiểu cho một thành viên mới trong nhóm. Đây không phải là những giả định. Đây là những vấn đề nhỏ nhặt nhưng bào mòn dự án, tuần này qua tuần khác. Trong phần mềm hiện đại, bạn gần như chắc chắn sẽ gặp phải YAML, JSON, hoặc TOML. Docker Compose sử dụng YAML. Các REST API chủ yếu dùng JSON. Trình quản lý gói Cargo của Rust đã chọn TOML. Mỗi lựa chọn này đều phản ánh một sự đánh đổi có chủ đích giữa khả năng con người đọc-viết, khả năng máy tính phân tích cú pháp, và khả năng biểu đạt. Sự khác biệt giữa một dự án dễ bảo trì và một dự án mong manh thường nằm ở việc hiểu rõ những sự đánh đổi đó, thay vì chỉ sao chép bất kỳ định dạng nào mà bài hướng dẫn đầu tiên bạn đọc sử dụng. Chúng ta sẽ so sánh cả ba định dạng dựa trên các khía cạnh thực sự quan trọng: cú pháp, kiểu dữ liệu, comment, chuỗi đa dòng, và công cụ hỗ trợ. Chúng ta cũng sẽ đề cập đến lý do tại sao bạn lại muốn chuyển đổi giữa chúng và, quan trọng hơn, những giới hạn thực sự của quá trình chuyển đổi đó. Tốt hơn hết là bạn nên biết những điều này trước khi bắt đầu.

JSON: Chặt chẽ, Tương thích và Cực kỳ khó viết tay

JSON, hay JavaScript Object Notation, có một đặc tính định hình: sự chặt chẽ. Nó được chuẩn hóa thành RFC 8259 vào năm 2017, nhưng linh hồn của nó đến từ đặc tả gốc của Douglas Crockford từ những năm 2000. Chỉ có duy nhất một cách để viết bất kỳ cấu trúc dữ liệu nào. Key phải nằm trong ngoặc kép. Dấu phẩy cuối cùng bị cấm. Còn comment? Chúng không tồn tại. Ít nhất là không có trong đặc tả. Trong giao tiếp giữa máy với máy, sự cứng nhắc đó là một tính năng, không phải lỗi. Tính dễ đoán của JSON là lý do tại sao nó thống trị thế giới REST API và các công cụ build. Mọi ngôn ngữ chính đều có một trình phân tích cú pháp trong thư viện chuẩn, và vì định dạng này rất rõ ràng, bạn có thể tin chắc rằng những gì bạn gửi đi chính là những gì họ sẽ nhận được. Nó cứ thế hoạt động thôi. Nỗi đau xuất hiện ngay khi con người phải tự tay viết nó. Bất kỳ ai đã từng dành mười phút để tìm một dấu phẩy bị thiếu trong một file `webpack.config.js` khổng lồ đều biết cảm giác này. Một khối cấu hình kết nối cơ sở dữ liệu đơn giản thì không sao: ```json { "database": { "host": "localhost", "port": 5432, "name": "myapp_production" } } ``` Nhưng hãy mở rộng nó lên 40 key, lồng nhau ba cấp, và quên một dấu phẩy? Trình phân tích cú pháp thường chỉ bỏ cuộc, trỏ bạn đến cuối file với một lỗi khó hiểu thay vì chỉ ra dòng có vấn đề thực sự. Việc thiếu comment cũng gây bực bội không kém. Mọi người phải dùng đến các mẹo như `"_comment": "cài đặt này kiểm soát TTL của cache"`, làm ô nhiễm mô hình dữ liệu chỉ để lại một ghi chú cho chính họ trong tương lai. Đây là lý do tại sao các định dạng như JSON5 và JSONC (JSON với Comment) tồn tại. Chúng rất phổ biến—file `settings.json` của VS Code sử dụng JSONC—nhưng về cơ bản chúng không theo chuẩn. Đừng bị lừa khi nghĩ rằng chúng là một lựa chọn mặc định an toàn. Nếu bạn gửi một file JSONC đến một trình phân tích cú pháp chuẩn, nó sẽ bị lỗi.

YAML: Dễ đọc tối đa, Cạm bẫy tối đa

YAML, viết tắt của YAML Ain't Markup Language, đặt khả năng con người đọc được lên hàng đầu. Nó loại bỏ các dấu ngoặc nhọn và ngoặc kép của JSON để thay bằng một cấu trúc dựa trên thụt lề sạch sẽ. Nó có comment gốc (`#`) và xử lý chuỗi đa dòng một cách mượt mà. Một workflow của GitHub Actions hay một manifest của Kubernetes được viết bằng YAML chắc chắn dễ đọc hơn cho con người so với phiên bản JSON tương đương của nó. Đây là cách mà cấu hình cơ sở dữ liệu đó trông như thế nào trong YAML: ```yaml database: host: localhost port: 5432 name: myapp_production # bộ replica được thêm vào 2024-03-10 replicas: - replica1.internal - replica2.internal ``` Nó sạch sẽ hơn, và comment quan trọng giải thích sự thay đổi là một thành phần hạng nhất. Chuỗi đa dòng cũng là một vấn đề đã được giải quyết bằng cách sử dụng các block scalar—ký tự `|` và `>`—giúp có thể nhúng các truy vấn SQL hoặc script shell vào trong file cấu hình mà không làm nó trở thành một mớ hỗn độn không thể đọc nổi. Nhưng sự dễ đọc này đi kèm với một cái giá, và cái giá đó được trả bằng sự mơ hồ và những nguy hiểm tiềm ẩn. Đây chính là những 'cạm bẫy' khét tiếng của YAML. Nổi tiếng nhất là 'vấn đề cờ Na Uy,' trong đó mã quốc gia `NO` bị phân tích thành giá trị boolean `false` theo mặc định trong các phiên bản YAML cũ (mà nhiều công cụ vẫn còn sử dụng!). Một dấu cách đặt sai vị trí trong phần thụt lề không gây ra lỗi; nó âm thầm thay đổi toàn bộ cấu trúc dữ liệu của bạn. Bản thân đặc tả của nó dài đến 86 trang một cách khủng khiếp—dài hơn cả đặc tả cho HTTP/1.1!—điều này cho bạn biết có bao nhiêu sự phức tạp ẩn giấu dưới bề mặt tưởng chừng đơn giản đó. Vậy điều này cho chúng ta kết luận gì? Đối với cơ sở hạ tầng dưới dạng mã (infrastructure-as-code) và các pipeline CI/CD, nơi các file cấu hình được con người viết và review cả ngày, sự dễ đọc của YAML thường xứng đáng với rủi ro. Nhưng đối với các cấu hình được tạo ra bởi code và hiếm khi được con người chạm vào, lợi thế đó biến mất, và bạn chỉ còn lại một cái bẫy chực chờ sập.

TOML: Lựa chọn thực tế ở giữa

TOML, hay Tom's Obvious, Minimal Language, là lựa chọn thực tế. Được tạo ra bởi nhà đồng sáng lập GitHub Tom Preston-Werner và hoàn thiện với phiên bản 1.0.0 vào năm 2021, toàn bộ lý do tồn tại của nó là để trở thành một định dạng cấu hình rõ ràng, không mơ hồ mà vẫn dễ đọc. Nó trông hơi giống một file INI kiểu cũ, với các tiêu đề phần trong ngoặc vuông, nhưng nó bổ sung các kiểu dữ liệu rõ ràng, đây là tính năng ăn tiền của nó. Cấu trúc này khuyến khích một file cấu hình phẳng hơn, vốn có thể là một ràng buộc tốt. ```toml [database] host = "localhost" port = 5432 name = "myapp_production" # bộ replica được thêm vào 2024-03-10 replicas = ["replica1.internal", "replica2.internal"] ``` Đây là nơi TOML trực tiếp giải quyết các vấn đề lớn nhất của YAML. Không có các chuỗi trần mơ hồ. `port = 5432` là một số nguyên. `enabled = true` là một giá trị boolean. `NO` chỉ là chuỗi 'NO'. Bạn không bao giờ phải lo lắng về việc một giá trị bị 'ép kiểu' một cách thần kỳ thành thứ mà bạn không mong muốn. Nó cũng có hỗ trợ hạng nhất cho kiểu dữ liệu ngày giờ, bao gồm cả dấu thời gian RFC 3339, đây là một sự giải thoát lớn cho bất kỳ ai đã từng phải xử lý các chuỗi ngày tháng. Điểm yếu chính của nó là việc biểu diễn dữ liệu lồng nhau sâu hoặc có tính lặp lại cao. Cú pháp `[[section]]` cho một mảng các bảng hoạt động tốt với các trường hợp đơn giản, như các mục `[[bin]]` của Cargo, nhưng nó nhanh chóng trở nên cồng kềnh. Nếu bạn cần ba hoặc bốn cấp lồng nhau, mô hình thụt lề của YAML đột nhiên trông hấp dẫn hơn nhiều. TOML cũng cố tình bỏ qua các tính năng như anchor và alias của YAML, vì vậy nếu bạn cần giảm sự trùng lặp giữa các môi trường, bạn hết cách rồi. Các file TOML lớn có thể trở nên rất lặp lại. Mức độ phổ biến khá tốt nhưng chưa phải là toàn diện. Python đã thêm `tomllib` vào thư viện chuẩn của mình trong phiên bản 3.11, một phiếu tín nhiệm lớn. Trước đó, bạn phải lấy một gói của bên thứ ba. Nếu nhóm của bạn làm việc sâu trong hệ sinh thái Rust, Python hiện đại, hoặc Go, TOML là một lựa chọn tuyệt vời, thoải mái. Đối với các dự án trải rộng trên nhiều ngôn ngữ khác nhau, bạn sẽ muốn kiểm tra sự sẵn có của các trình phân tích cú pháp trước khi quyết định sử dụng.

Đối đầu trực tiếp: Bảng so sánh tính năng

Hãy đặt tất cả các sự đánh đổi vào một nơi. Đây là so sánh trực tiếp các tính năng gây đau đầu nhất trong các file cấu hình thực tế. **Comment (Chú thích):** YAML & TOML: Có, thông qua comment trên dòng bằng `#`. JSON: Không. Không có trong đặc tả, và sẽ không bao giờ có. Chấp nhận đi. **Dấu phẩy cuối cùng:** YAML & TOML: Không áp dụng, vì chúng không sử dụng dấu phẩy làm dấu phân cách chính. JSON: Bị cấm. Một nguồn kinh điển gây ra các git diff khó chịu và lỗi phân tích cú pháp. **Chuỗi đa dòng:** YAML: Hỗ trợ tuyệt vời thông qua các block scalar (`|`, `>`), mặc dù các chỉ báo chomping (`-`, `+`) có thể hơi khó dùng. TOML: Hỗ trợ tốt với các chuỗi trong ba dấu ngoặc kép. JSON: Tệ hại. Bạn bị mắc kẹt với việc phải thêm các ký tự thoát `\n` thủ công. Nó xấu xí và dễ gây lỗi. **Kiểu dữ liệu ngày và giờ:** TOML: Có, các đối tượng datetime hạng nhất. YAML: Có, trong phiên bản 1.2, nhưng hỗ trợ từ các parser có thể không nhất quán. JSON: Không. Ngày tháng chỉ là các chuỗi theo quy ước, và bạn phải tự phân tích chúng. **Anchor và alias (cấu hình DRY):** YAML: Có, với `&anchor` và `*alias`. Một tính năng mạnh mẽ nhưng thường khó hiểu để giảm sự lặp lại. TOML & JSON: Không. Bạn phải tự lặp lại hoặc dựa vào một bước tiền xử lý. **Xác thực Schema:** JSON: Có, với JSON Schema, một tiêu chuẩn đã trưởng thành và được hỗ trợ rộng rãi. YAML: Có thể sử dụng JSON Schema, nhưng yêu cầu công cụ cụ thể (`ajv`, `yamale`). TOML: Công cụ hỗ trợ ở mảng này kém phát triển hơn nhiều. Đây là một điểm yếu rõ ràng. **Kích thước file cho dữ liệu tương đương:** JSON là nhỏ gọn nhất sau khi được rút gọn (minify). Đối với các file mà con người có thể đọc, YAML và TOML là tương đương. Sự khác biệt hiếm khi nhiều hơn 10-15% đối với một file cấu hình thông thường, vì vậy đây không phải là yếu tố quyết định chính. **Tốc độ phân tích cú pháp:** Để đọc một file cấu hình khi ứng dụng khởi động, tất cả đều đủ nhanh. Đối với việc tuần tự hóa thông lượng cao (hàng nghìn hoạt động/giây), JSON là nhà vô địch không thể tranh cãi, nhờ vào các thư viện được tối ưu hóa cao như `simdjson`.

Chuyển đổi giữa các định dạng: Cái gì hoạt động và cái gì không

Sớm hay muộn, bạn sẽ cần chuyển đổi giữa các định dạng này. Có thể bạn đang di chuyển một dự án, tạo một file cấu hình YAML từ một API JSON, hoặc cung cấp một file TOML cho một công cụ cũ chỉ hiểu JSON. Chuyện đó vẫn xảy ra. CocoConvert có thể xử lý phần việc cơ khí này cho bạn với các công cụ chuyển đổi JSON-sang-YAML, YAML-sang-JSON, TOML-sang-JSON, và JSON-sang-TOML. Đối với các cấu hình đơn giản với các kiểu dữ liệu cơ bản, việc chuyển đổi là không mất dữ liệu và nhanh chóng. Chỉ cần tải file của bạn lên trang Chuyển đổi, chọn định dạng mục tiêu, và thực hiện. Nhưng việc chuyển đổi tự động có những giới hạn cứng. Bạn cần phải nhận thức được những gì bị thất lạc trong quá trình chuyển đổi, vì đó không phải là lỗi của công cụ—đó là sự không tương thích cơ bản giữa các định dạng. **Comment sẽ bị mất khi chuyển sang JSON.** JSON không có khái niệm về comment. Chấm hết. Khi bạn chuyển đổi một file YAML hoặc TOML có comment sang JSON, những comment đó sẽ biến mất vĩnh viễn. Không có cách giải quyết nào khác; đó là một hạn chế của chính đặc tả JSON. **Các anchor của YAML được bung ra, không được bảo toàn.** Nếu file YAML của bạn sử dụng `&defaults` và `*defaults` để giữ cho code không bị lặp lại (DRY), cấu trúc đó sẽ bị mất. Công cụ chuyển đổi sẽ bung các anchor ra, nghĩa là dữ liệu đầu ra là giống hệt, nhưng nó sẽ bị lặp lại ở mọi nơi. File kết quả sẽ hoạt động, nhưng nó sẽ không còn dễ bảo trì như trước. **Kiểu datetime của TOML có thể trở thành chuỗi.** Kiểu `created_at = 2024-01-15T09:30:00Z` gốc của TOML không có tương đương trực tiếp trong JSON. CocoConvert sẽ biến nó thành một chuỗi ISO 8601 tiêu chuẩn, đó là quy ước chính xác. Nhưng ứng dụng đọc file JSON đó cần đủ thông minh để biết rằng nó nên phân tích chuỗi đó trở lại thành một đối tượng ngày tháng. **Chuyển đổi TOML lồng sâu sang YAML** hoạt động tốt về mặt cấu trúc, nhưng đầu ra có thể gây ngạc nhiên. Cú pháp `[[array of tables]]` của TOML ánh xạ tới một chuỗi các ánh xạ (sequence of mappings) trong YAML, điều này có thể trông lạ nếu bạn không quen nhìn thấy nó. Luôn xem lại đầu ra đã chuyển đổi trước khi tin tưởng sử dụng nó trong môi trường production. Một lệnh `diff` nhanh với file mà bạn đã chuyển đổi qua lại (một vòng lặp) là cách tốt nhất để phát hiện bất kỳ sự bất ngờ nào.

Đưa ra quyết định: Định dạng nào cho tình huống nào

Không có câu trả lời đúng duy nhất, nhưng đừng để điều đó đánh lừa bạn rằng sự lựa chọn này không quan trọng. Những quy tắc chung rõ ràng đã hình thành về việc khi nào nên sử dụng mỗi định dạng. **Sử dụng JSON khi:** Cấu hình dành cho máy, không phải cho người. Nếu nó được tạo ra và tiêu thụ bởi các công cụ, và con người hiếm khi chỉnh sửa nó bằng tay, sự chặt chẽ của JSON là một đức tính tốt. Đây là lý do tại sao `package.json` và `tsconfig.json` là JSON. Mục tiêu là khả năng tương thích tối đa. **Sử dụng YAML khi:** Con người là đối tượng chính. Đối với các manifest của Kubernetes, GitHub Actions, hoặc playbook của Ansible, khả năng đọc được là tối quan trọng. Cấu hình được viết và review liên tục bởi mọi người. Vâng, những cạm bẫy là có thật, nhưng chúng có thể quản lý được. Một khuyến nghị không thể thương lượng của tôi: nếu bạn sử dụng YAML, bạn *bắt buộc* phải thực thi một linter như `yamllint` trong pipeline CI của mình. Không có ngoại lệ. **Sử dụng TOML khi:** Bạn muốn sự dễ đọc của YAML mà không có sự mơ hồ nguy hiểm của nó. Nếu cấu hình của bạn chủ yếu là phẳng và nhóm của bạn làm việc trong một hệ sinh thái có hỗ trợ tốt (như Rust với Cargo.toml hoặc Python hiện đại với pyproject.toml), TOML là một lựa chọn tuyệt vời. Nó cũng là định dạng thân thiện nhất cho người dùng cuối của ứng dụng của bạn chỉnh sửa, vì cú pháp đơn giản và các lỗi từ trình phân tích cú pháp thường hữu ích. **Khi không có định dạng nào trong ba cái hoàn toàn phù hợp:** Hãy lùi lại một bước và tự hỏi liệu một file cấu hình tĩnh có phải là công cụ phù hợp hay không. Các cặp key-value đơn giản thường có thể nằm trong các biến môi trường. Đối với các kịch bản thực sự phức tạp với logic có điều kiện, việc cố nhồi nhét nó vào YAML hay TOML là một sai lầm. Bạn đã vượt qua giới hạn của chúng rồi. Đã đến lúc thừa nhận rằng bạn cần một ngôn ngữ lập trình thực sự cho cấu hình của mình, cho dù đó là một ngôn ngữ cấu hình chuyên dụng như Dhall hay Starlark, hoặc chỉ là một script Python đơn giản. Đó là một cam kết lớn hơn, nhưng còn hơn là tạo ra một con quái vật từ file YAML lồng nhau. Nếu bạn thấy mình bị mắc kẹt với một quyết định sai lầm giữa dự án, CocoConvert có thể xử lý việc di chuyển. Tuy nhiên, lựa chọn chiến lược về định dạng nào phục vụ tốt nhất cho nhóm của bạn vẫn phụ thuộc vào bạn.