All posts

TOML vs YAML vs JSON: Choosing the Right Config Format

July 27, 2026 · DevTools

toml
yaml
json
configuration
developer-tools

You have a Cargo.toml, a docker-compose.yml, and a tsconfig.json open side by side. They all look like configuration. They all sort of look like each other. But one of them will eat your # TODO: fix this comment on save, one has trouble with dates, and one refuses a trailing comma. The question is not which format is "best" — it is which format is right for what you are trying to express.

You can validate any TOML file right now with the TOML Validator — it parses in your browser with line-level error reporting.

What each format actually is

JSON (JavaScript Object Notation, RFC 8259) is a data interchange format. It was designed for machines talking to machines: strict syntax, no comments, no trailing commas, no loose rules. It is excellent at representing structured data and is universally understood. It is a poor choice for human-maintained configuration because you cannot annotate it.

YAML (YAML Ain't Markup Language) was designed as a human-friendly superset of JSON. The same JSON is valid YAML. The appeal is readable, expressive syntax with support for comments, multi-line strings, anchors, and references. The trap is that YAML is genuinely complex: indentation is significant, and the spec has ambiguous corners (the yes/no boolean problem, unquoted strings that parse as dates). YAML is fine for configuration, but it demands care.

TOML (Tom's Obvious, Minimal Language) was designed explicitly as a config file format. It reads like INI evolved with types: comments, tables, arrays, inline tables, date-time values, and multiline strings — all built in. The spec is small and unambiguous. Rust (Cargo), Python (pyproject.toml), and many modern tools adopted it precisely because it gives you the expressiveness of YAML without most of its footguns.

The practical differences that bite you

Comments. JSON has none. YAML has them. TOML has them. If your config needs # do not change this or // temporarily disabled, JSON is immediately out.

Types. TOML distinguishes 3.14 as a float, "3.14" as a string, and [3.14] as an array of floats. YAML conflates these — a bare 3.14 is a float, but so is 3.14.0, which you probably wanted as a string. This ambiguity is a real source of bugs in YAML configs with version strings like v1.2.3.

Dates and times. TOML has first-class date, time, and datetime types. YAML added them in 1.1 but most YAML parsers treat 2024-01-15 as a string. If you are expressing a schedule, an expiry, or a release timestamp, TOML wins.

Tables vs. nesting. TOML's [package.metadata.docs] is explicit and readable. The YAML equivalent under-indents into the same namespace. This is where TOML's "obvious" design pays off: the structure is always visible.

What does not translate. Comments and date-time values disappear when converting TOML to JSON — JSON simply cannot represent them. TOML multiline strings with trailing whitespace or newlines lose that whitespace in YAML because YAML folds them. Dates in TOML may become strings in YAML. If you round-trip between formats, audit the output.

A side-by-side comparison

# TOML — Cargo.toml
[package]
name = "my-crate"
version = "0.1.0"
edition = "2021"
rust-version = "1.70"

[dependencies]
serde = { version = "1.0", features = ["derive"] }

[package.metadata.docs.rs]
all-features = true
# YAML equivalent (lossy)
package:
  name: my-crate
  version: "0.1.0"
  edition: "2021"
  rust-version: "1.70"

dependencies:
  serde:
    version: "1.0"
    features:
      - derive

package.metadata.docs.rs:
  all-features: true

The YAML version is valid, but 0.1.0 is now a float in some parsers. The comment is gone. The date-type metadata fields (if added) would disappear. Cargo reads TOML because TOML was designed to express exactly this shape without ambiguity.

When to use each

Use JSON for machine-to-machine APIs, generated config, and data files where you control the producer and want zero ambiguity. Not for human-edited files.

Use YAML for complex config where you need anchors, references, or nested structures and your team is comfortable with the spec gotchas. CI/CD pipelines (GitHub Actions, Ansible) are YAML because the tooling predates the TOML spec's maturity. Do not use YAML for anything where a number like 08 appearing in a string matters — always quote.

Use TOML for language-specific project config: Cargo.toml, pyproject.toml, npm-package metadata. Use it for new projects where you control the tooling and want a small, predictable spec. If your config needs comments (it will), TOML is the minimum viable choice.

Validating your TOML

The most common TOML errors are:

  • Inline tables vs. tables: { key = "value" } is an inline table; it cannot be followed by further keys in the same group. Use [table] for extendable groups.
  • Duplicate keys: TOML rejects [package] appearing twice in the same file. A common copy-paste mistake.
  • Dotted keys: array[1].field = "value" is valid TOML 1.0 for updating nested array items. The brackets syntax is required.
# Valid TOML
[dependencies]
serde = "1.0"

[dependencies.optional-tests]
serde = { version = "1.0", optional = true }

Paste your Cargo.toml or pyproject.toml into the TOML Validator to catch these before your build does.

The converter is useful too

When you need to feed a TOML config into a JSON-based tool — a CI system, a code generator, a schema validator — the TOML to JSON Converter handles the translation. Remember to audit the output for lost comments and date values.

The right format is the one your tooling requires and your team can maintain without errors. For Rust and Python projects, that is TOML.