JSON Converter ยท 6 min read
TOML vs YAML vs JSON: choosing the right config file format
TOML, YAML, and JSON each make a different tradeoff between strictness, readability, and expressiveness. The right choice depends on who edits the file and what consumes it.
Configuration file formats are not interchangeable. Each of the three dominant formats โ TOML, YAML, and JSON โ was designed with a different primary use case in mind, and each carries tradeoffs that matter in practice. Choosing the wrong one for a project generates friction that compounds over time.
TOML: the case for explicitness
TOML (Tom's Obvious, Minimal Language) was created by Tom Preston-Werner, co-founder of GitHub and creator of Semantic Versioning, in 2013. Preston-Werner's stated motivation was dissatisfaction with both YAML's implicit type system and JSON's lack of comments. TOML version 1.0.0 was released in 2021 after years of stabilisation.
TOML's key properties are explicit typing, a flat structure using sections (called tables), and a syntax deliberately designed to parse unambiguously. Values are typed by their literal syntax: strings use quotes, integers are bare numbers, booleans are true or false, dates follow RFC 3339. There is no implicit coercion.
# Cargo.toml (Rust package manifest)
[package]
name = "my-crate"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }TOML is the configuration format for Rust's Cargo build system, Python's pyproject.toml, and Hugo static site configuration. It is the right choice when the file will be edited by developers, when type safety in configuration matters, and when the format will be maintained over a long project lifespan.
YAML: the case for expressiveness
YAML's primary advantage is its native support for comments, multi-line strings, and anchors and aliases. Anchors allow you to define a value or block once (using &anchor-name) and reference it elsewhere in the same file (using *anchor-name). This is particularly useful in Docker Compose files and CI/CD pipelines where multiple jobs share common configuration blocks.
# Docker Compose with anchors
x-common-env: &common-env
LOG_LEVEL: info
DATABASE_URL: postgres://db:5432/app
services:
web:
image: my-app:latest
environment:
<<: *common-env
worker:
image: my-app:latest
environment:
<<: *common-envThe tradeoff is complexity. YAML's implicit type coercion (the Norway Problem โ where the country code NO is parsed as boolean false in YAML 1.1 parsers), indentation sensitivity, and multiple valid representations of the same data make YAML files fragile at scale. Large organisations typically add linting (yamllint), schema validation, and templating layers (Helm for Kubernetes) on top of raw YAML to manage this complexity.
JSON: the case for strictness
JSON has one overwhelming advantage: universal tooling support. Every programming language has a JSON parser in its standard library or a widely trusted third-party equivalent. JSON has a formal grammar specification (RFC 8259), no implicit type coercion, no alternative syntaxes, and no ambiguity. A valid JSON document parsed by any compliant parser produces identical results.
JSON's limitations in configuration contexts are intentional design choices. No comments, no trailing commas, no multi-line strings, and no references. Douglas Crockford omitted comments deliberately: he believed that if comments were allowed, people would use them to carry parsing directives (as XML processing instructions did), which would break interoperability. For package.json, ESLint configs, and tsconfig files, these limitations are accepted costs of universal tool compatibility.
Decision matrix
| Factor | TOML | YAML | JSON |
|---|---|---|---|
| Comments | Yes | Yes | No |
| Implicit type coercion | No | Yes (YAML 1.1) | No |
| Multi-line strings | Yes (triple quotes) | Yes (block scalars) | Escape only |
| References / anchors | No | Yes | No |
| Tooling support | Good | Excellent | Universal |
| Learning curve | Low | Medium | Low |
| Primary use case | Developer config | Infrastructure / CI | Data interchange |
When each format is the right choice
Use TOML for developer-facing configuration files in software projects: Rust (Cargo.toml), Python (pyproject.toml), or any project where clarity and strict typing are more important than ecosystem compatibility. TOML is also well-suited to static site generators and tools that need a clean, unambiguous config format.
Use YAML when the ecosystem has standardised on it (Kubernetes, Ansible, GitHub Actions, Docker Compose) or when comments and multi-line values are essential. Accept that you will need linting and validation tooling to manage YAML safely at scale.
Use JSONwhen the file will be consumed by many different tools or languages, when interoperability is non-negotiable, or when the ecosystem has standardised on it (npm, Node.js tooling, REST APIs). JSON is also the right choice when the "config file" is actually a data document โ a list of feature flags, a schema, or a manifest consumed by a build tool.
The practical answer for most projects
Most projects do not get to choose a single format. A typical web service uses package.json (JSON, non-negotiable), .github/workflows (YAML, non-negotiable if using GitHub Actions), and may add a pyproject.toml or similar developer config. The decision is rarely global โ it is made file by file, constrained by the tools in use. When you do have a choice, TOML for developer config and JSON for machine-consumed data is a defensible default.
References
- Preston-Werner, T. (2023). TOML: Tom's Obvious, Minimal Language, version 1.0.0. toml.io.
- Ben-Kiki, O., Evans, C., & Ingerson, B. (2021). YAML Ain't Markup Language (YAML) version 1.2.2. yaml.org.
- Crockford, D. (2017). How JavaScript Works. Virgule Solidus.
- Klabnik, S. & Nichols, C. (2022). The Rust Programming Language (2nd ed.). No Starch Press.
- GitHub. (2024). Workflow syntax for GitHub Actions. docs.github.com.