Skip to content
Back to Blog
format-comparisons

YAML vs JSON vs TOML: Choosing a Config Format

2026-05-17 9 min read

Why Config Format Choice Actually Matters

Pick the wrong configuration format, and you'll spend hours debugging a missing comma. You'll fight with a parser that silently drops a key. You'll have to explain the arcane indentation rules to a new team member, again. These aren't hypotheticals. These are the small papercuts that bleed projects dry, week after week. In modern software, you'll almost always run into YAML, JSON, or TOML. Docker Compose uses YAML. REST APIs overwhelmingly speak JSON. Rust's Cargo package manager chose TOML. Each of these choices reflects a deliberate trade-off between human writability, machine parseability, and expressive power. The difference between a maintainable project and a fragile one often comes down to understanding those trade-offs, instead of just copying whatever format the first tutorial used. We'll compare all three across the dimensions that actually matter: syntax, data types, comments, multi-line strings, and tooling. We'll also cover why you'd convert between them and, more importantly, the real limits of that conversion process. It's better to know those before you start.

JSON: Strict, Portable, and Surprisingly Painful to Write by Hand

JSON, or JavaScript Object Notation, has one defining characteristic: strictness. It was standardized as RFC 8259 in 2017, but its soul comes from Douglas Crockford's original spec from the early 2000s. There's exactly one way to write any given data structure. Keys require double quotes. Trailing commas are forbidden. And comments? They don't exist. Not in the spec, anyway. In machine-to-machine communication, that rigidness is a feature, not a bug. JSON's predictability is why it conquered the world of REST APIs and build tooling. Every major language has a standard library parser, and because the format is so unambiguous, you can be confident that what you send is what they'll get. It just works. The pain arrives the moment a human has to write it. Anyone who's spent ten minutes hunting for a missing comma in a giant `webpack.config.js` knows this feeling. A simple database connection block is fine: ```json { "database": { "host": "localhost", "port": 5432, "name": "myapp_production" } } ``` But scale that up to 40 keys, nest it three levels deep, and forget one comma? The parser often just gives up, pointing you to the end of the file with a cryptic error instead of the actual problem line. The lack of comments is just as frustrating. People resort to hacks like `"_comment": "this setting controls cache TTL"`, polluting the data model just to leave a note for their future selves. This is why formats like JSON5 and JSONC (JSON with Comments) exist. They're popular—VS Code's `settings.json` uses JSONC—but they are fundamentally non-standard. Don't be fooled into thinking they are a safe default. If you send a JSONC file to a standard parser, it's going to crash.

YAML: Maximum Readability, Maximum Footguns

YAML, which stands for YAML Ain't Markup Language, puts human readability first. It throws out the braces and quotes of JSON in favor of a clean, indentation-based structure. It has native comments (`#`) and handles multi-line strings with grace. A GitHub Actions workflow or a Kubernetes manifest written in YAML is undeniably easier for a human to scan than its JSON counterpart. Here's how that same database config feels in YAML: ```yaml database: host: localhost port: 5432 name: myapp_production # replica set added 2024-03-10 replicas: - replica1.internal - replica2.internal ``` It's cleaner, and the crucial comment explaining the change is a first-class citizen. Multi-line strings are also a solved problem using block scalars—the `|` and `>` characters—making it possible to embed SQL queries or shell scripts in a config without it becoming an unreadable mess. But this readability comes with a price, and it's paid in ambiguity and hidden dangers. These are the infamous YAML 'footguns.' The most famous is the 'Norwegian flag problem,' where the country code `NO` is parsed as the boolean `false` by default in older YAML versions (which many tools still use!). A single misplaced space in the indentation doesn't throw an error; it silently changes the entire structure of your data. The spec itself is a monstrous 86 pages long—longer than the spec for HTTP/1.1!—which tells you just how much complexity is hiding under that seemingly simple surface. So where does that leave us? For infrastructure-as-code and CI/CD pipelines, where config files are written and reviewed by humans all day long, YAML's readability is often worth the risk. But for configs generated by code and rarely touched by hand, that advantage evaporates, and you're left holding a loaded footgun.

TOML: The Pragmatic Middle Ground

TOML, or Tom's Obvious, Minimal Language, is the pragmatic choice. Created by GitHub co-founder Tom Preston-Werner and finalized as version 1.0.0 in 2021, its entire reason for being is to be an obvious, unambiguous config format that's still pleasant to read. It looks a bit like an old-school INI file, with section headers in square brackets, but it adds explicit data types, which is its killer feature. This structure encourages a flatter config, which can be a healthy constraint. ```toml [database] host = "localhost" port = 5432 name = "myapp_production" # replica set added 2024-03-10 replicas = ["replica1.internal", "replica2.internal"] ``` This is where TOML directly answers YAML's biggest problems. There are no ambiguous bare strings. `port = 5432` is an integer. `enabled = true` is a boolean. `NO` is just the string 'NO'. You never have to worry about a value being magically coerced into something you didn't intend. It also has first-class support for datetimes, including RFC 3339 timestamps, which is a huge relief for anyone who's had to deal with date strings. Its main weakness is representing deeply nested or highly repetitive data. The `[[section]]` syntax for an array of tables works for simple cases, like Cargo's `[[bin]]` entries, but it gets clumsy fast. If you need three or four levels of nesting, YAML's indentation model suddenly looks a lot more appealing. TOML also deliberately omits features like YAML's anchors and aliases, so if you need to reduce duplication across environments, you're out of luck. Large TOML files can get very repetitive. Adoption is solid but not universal. Python added `tomllib` to its standard library in 3.11, a major vote of confidence. Before that, you had to grab a third-party package. If your team is deep in the Rust, modern Python, or Go ecosystems, TOML is an excellent, comfortable choice. For projects spanning many different languages, you'll want to check on parser availability first before you commit.

Head-to-Head: A Feature Comparison Table

Let's put all the trade-offs in one place. Here's a direct comparison of the features that cause the most headaches in real-world config files. **Comments:** YAML & TOML: Yes, via `#` line comments. JSON: No. Not in the spec, not ever. Deal with it. **Trailing commas:** YAML & TOML: Not applicable, as they don't use commas as primary delimiters. JSON: Forbidden. A classic source of frustrating git diffs and parse errors. **Multi-line strings:** YAML: Excellent support via block scalars (`|`, `>`), though the chomping indicators (`-`, `+`) can be tricky. TOML: Good support with triple-quoted strings. JSON: Awful. You're stuck manually adding `\n` escapes. It's ugly and error-prone. **Date and time types:** TOML: Yes, first-class datetime objects. YAML: Yes, in version 1.2, but parser support can be inconsistent. JSON: No. Dates are just strings by convention, and it's up to you to parse them. **Anchors and aliases (DRY configs):** YAML: Yes, with `&anchor` and `*alias`. A powerful but often confusing feature for reducing repetition. TOML & JSON: No. You either repeat yourself or rely on a pre-processing step. **Schema validation:** JSON: Yes, with JSON Schema, a mature and widely supported standard. YAML: Can use JSON Schema, but requires specific tooling (`ajv`, `yamale`). TOML: Tooling is much less mature here. It's a clear weak spot. **File size for equivalent data:** JSON is the most compact once minified. For human-readable files, YAML and TOML are comparable. The difference is rarely more than 10-15% for a typical config, so this is not a major deciding factor. **Parsing speed:** For reading a config at app startup, all are plenty fast. For high-throughput serialization (thousands of ops/sec), JSON is the undisputed champion, thanks to highly optimized libraries like `simdjson`.

Converting Between Formats: What Works and What Doesn't

Sooner or later, you'll need to convert between these formats. Maybe you're migrating a project, generating a YAML config from a JSON API, or feeding a TOML file to a legacy tool that only speaks JSON. It happens. CocoConvert can handle the mechanical work for you with its JSON-to-YAML, YAML-to-JSON, TOML-to-JSON, and JSON-to-TOML converters. For simple configs with basic data types, the conversion is lossless and fast. Just upload your file on the Convert page, pick your target format, and go. But automated conversion has hard limits. You need to be aware of what gets lost in translation, because it's not a bug in the tool—it's a fundamental mismatch between the formats. **Comments are lost going to JSON.** JSON has no concept of comments. Period. When you convert a commented YAML or TOML file to JSON, those comments are gone forever. There is no workaround; it's a limitation of the JSON spec itself. **YAML anchors are expanded, not preserved.** If your YAML file uses `&defaults` and `*defaults` to stay DRY, that structure will be lost. The converter will expand the anchors, meaning the output data is identical, but it will be duplicated everywhere. The resulting file will work, but it won't be as maintainable. **TOML datetimes may become strings.** TOML's native `created_at = 2024-01-15T09:30:00Z` has no direct equivalent in JSON. CocoConvert will turn it into a standard ISO 8601 string, which is the correct convention. But the application reading that JSON needs to be smart enough to know it should parse that string back into a date object. **Deeply nested TOML to YAML** works fine structurally, but the output can be surprising. TOML's `[[array of tables]]` syntax maps to a YAML sequence of mappings, which can look odd if you're not used to seeing it. Always review the converted output before trusting it in production. A quick `diff` against a file you've converted back and forth (a round trip) is the best way to catch any surprises.

Making the Call: Which Format for Which Situation

There's no single right answer, but don't let that fool you into thinking the choice doesn't matter. Clear heuristics have emerged for when to use each format. **Use JSON when:** The config is for machines, not people. If it's being generated and consumed by tools, and humans will rarely edit it by hand, JSON's strictness is a virtue. This is why `package.json` and `tsconfig.json` are JSON. Maximum compatibility is the goal. **Use YAML when:** Humans are the primary audience. For Kubernetes manifests, GitHub Actions, or Ansible playbooks, readability is paramount. The config is written and reviewed constantly by people. Yes, the footguns are real, but they are manageable. My one non-negotiable recommendation: if you use YAML, you *must* enforce a linter like `yamllint` in your CI pipeline. No exceptions. **Use TOML when:** You want YAML's readability without its dangerous ambiguity. If your config is mostly flat and your team works in an ecosystem with good support (like Rust with Cargo.toml or modern Python with pyproject.toml), TOML is a fantastic choice. It's also the friendliest format for end-users of your application to edit, as the syntax is simple and parser errors are generally helpful. **When none of the three fits perfectly:** Take a step back and ask if a static config file is even the right tool. Simple key-value pairs can often live in environment variables. For truly complex scenarios with conditional logic, trying to shoehorn it into YAML or TOML is a mistake. You've outgrown them. It's time to admit you need a real programming language for your configuration, whether that's a dedicated config language like Dhall or Starlark, or just a simple Python script. It's a bigger commitment, but it's better than building a monster out of nested YAML. If you find yourself stuck on the wrong side of one of these decisions mid-project, CocoConvert can handle the migration. The strategic choice of which format best serves your team, however, is still up to you.