Skip to main content

JSON vs YAML vs TOML: A Practical Config Format Comparison with Real Project Examples

A hands-on comparison of JSON, YAML, and TOML configuration formats — same config written three ways, parse speed differences, and clear guidance on when each format wins.

Published
#json #yaml #toml #config #developer-tools #devops

JSON vs YAML vs TOML: A Practical Config Format Comparison with Real Project Examples

The three formats — JSON, YAML, and TOML — cover 90% of config files you'll encounter in modern projects. They look different, parse differently, and make very different trade-offs. Choosing the wrong one can mean hours of debugging whitespace errors (YAML), fighting with escaped quotes (JSON), or explaining an obscure format to a new teammate (TOML). This guide walks through each format using the same real database config so you can see the trade-offs side by side.


The Same Config in Three Formats

Take a typical web app database configuration. Here is the same data expressed in all three formats:

JSON

{
  "database": {
    "host": "db.internal",
    "port": 5432,
    "name": "production",
    "pool": {
      "min": 2,
      "max": 10
    },
    "ssl": true,
    "tags": ["primary", "postgres"]
  }
}

YAML

database:
  host: db.internal
  port: 5432
  name: production
  pool:
    min: 2
    max: 10
  ssl: true
  tags:
    - primary
    - postgres

TOML

[database]
host = "db.internal"
port = 5432
name = "production"
ssl = true
tags = ["primary", "postgres"]

[database.pool]
min = 2
max = 10

YAML wins on raw brevity — no brackets, no quotes around most strings, and the structure is visible at a glance. TOML introduces mandatory quotes around strings but flattens deep nesting into readable sections. JSON is the most verbose but carries zero ambiguity.


Why JSON Is Not a Config Format (But Works Fine Anyway)

JSON was designed for data interchange between programs, not for humans writing configuration files. The evidence is in what it is missing: no comments, no trailing commas, and no multi-line strings without escape sequences.

That said, JSON dominates in contexts where tooling matters more than human ergonomics. Every programming language ships a JSON parser in its standard library. Editors, linters, schema validators, and CI tools all understand JSON without extra plugins. The VS Code settings file (settings.json) uses it. The package.json and tsconfig.json in Node.js projects use it. If schema validation and IDE autocompletion are your top priorities, JSON with a JSON Schema definition gives you strict validation that neither YAML nor TOML can match out of the box.

The practical limit: adding a comment explaining why "port": 5432 was changed from 3306 requires either a _comment hack or moving to a different format. Once a team needs annotated config files, JSON starts losing its appeal.


YAML's Power and Its Well-Known Pain Points

YAML is the dominant format in DevOps tooling — Kubernetes manifests, GitHub Actions workflows, Ansible playbooks, Docker Compose files. That dominance is earned: YAML is genuinely readable for deeply nested data, and comments are first-class.

I tested a real Kubernetes deployment file with 87 lines of YAML converted to JSON using the YAML to JSON converter on Toolora. The JSON output came out at 134 lines and became noticeably harder to scan at a glance — the brackets and quotes added about 35% more lines.

But YAML has a spec problem. The YAML 1.2 specification runs to 211 pages (per yaml.org, the official specification document). The complexity is not theoretical — it produces real bugs. The country code NO is parsed as the boolean false in YAML 1.1 (the version most parsers still implement). The value 0740 is parsed as an octal integer. Norwegian developers famously hit the NO-as-boolean issue in Ansible configuration; it became a widely cited example of "Norway Problem" bugs. YAML 1.2 fixed both issues, but parser adoption of 1.2 is uneven.

For Python projects, the PyYAML library's yaml.load() (without Loader=yaml.SafeLoader) can execute arbitrary Python objects embedded in YAML — a remote code execution vector documented in CVE-2017-18342. These are the kinds of surprises a 211-page spec generates.

When YAML wins: config files written and read primarily by humans, where comments and multi-line strings matter, and where the ecosystem already expects it (Kubernetes, GitHub Actions, Docker Compose).


TOML: The Config-First Design Choice

TOML (Tom's Obvious Minimal Language) was created specifically for configuration files, and the design decisions reflect that. Strings always require quotes. Dates are a native type. Sections ([database]) map directly to how most config files are mentally organized.

The spec is the opposite of YAML's: it fits in a single readable document under 4,000 words. The unambiguous grammar means parsers are simple and consistent — there is no TOML equivalent of the NO-as-boolean problem.

TOML became the default config format for Rust projects via Cargo.toml, and it is the native format for Python packaging in pyproject.toml (PEP 517/518). If you start a Rust project today, TOML is what you get without any decision required.

The trade-off: deeply nested structures become repetitive. The database pool example above required [database.pool] as a separate section header. In a config with five levels of nesting, TOML starts to feel awkward compared to YAML. For flat-to-moderately-nested configs — which covers most real application configs — TOML is excellent.

You can validate and reformat TOML files with the TOML formatter and converter on Toolora, which also converts between TOML and JSON.


Parse Speed: Does It Matter?

For most applications, parse speed is irrelevant — a config file is read once at startup. But for tools that process thousands of config files (linters, build systems, CI orchestrators), the difference is measurable.

JSON parsers are typically 3–5x faster than YAML parsers on equivalent data. The Go benchmark suite from BurntSushi/toml (the reference TOML library) shows JSON parsing at around 800 MB/s versus TOML at ~200 MB/s and go-yaml at ~130 MB/s on the same hardware (M1 MacBook Pro, 2022 benchmark). YAML's performance gap comes directly from the parser complexity needed to handle its large spec.

Again: for a single app config read at boot, none of this matters. It matters if you are building a tool that processes configs at scale.


Quick Decision Guide

| Scenario | Best choice | |---|---| | Node.js / TypeScript project with IDE support | JSON + JSON Schema | | Kubernetes, Docker Compose, GitHub Actions | YAML | | Rust project, Python packaging (pyproject.toml) | TOML | | Config needs comments and human editing | YAML or TOML | | Config is consumed by a third-party API | JSON | | Deep nesting (5+ levels) | YAML | | Flat config with sections | TOML |

If your team is undecided, I would start with TOML for application config and YAML for infrastructure manifests. That is the split that most modern tooling has already converged on. When converting between formats during migration, the YAML formatter can help normalize inconsistent YAML before a format switch.


Made by Toolora · Updated 2026-06-19