NV ToolkitFormat · Validate · Convert
Guide

TOML vs YAML vs JSON: Choosing a Configuration Format

Every new project eventually needs a configuration file, and three formats dominate the choice: TOML, YAML and JSON. They can all represent the same data, so the decision isn't about capability — it's about which trade-offs (comments, strictness, nesting, tooling) fit how the file will actually be read, written and reviewed.

In short

Short answer: reach for TOML when a human edits a flat-ish, fixed-schema config file by hand (pyproject.toml, Cargo.toml); reach for YAML when the config is deeply nested, hand-maintained, and comments matter more than strict typing (Kubernetes, CI pipelines); reach for JSON when a program writes or reads the file, not a person. All three convert into each other losslessly except for comments, TOML's missing null, and YAML's anchors.

By NaveenKumar T · Software engineer · builds and maintains NV Toolkit Published 2026-09-18 6 min read

The same settings written three ways

Take a small, realistic config — a server port, a database URL, a list of allowed origins, and a nested logging section — and write it in each format. The shapes converge on the same data; what differs is punctuation, and punctuation turns out to matter a lot once a file is edited by hand every week.

TOML reads like an .ini file that grew real types: [headings] introduce tables, key = value pairs are unambiguous, and there's no meaningful whitespace to get wrong. The YAML rendering of the same config appears in the next section, so each format gets its own code block rather than three squeezed into one.

toml
[server]
port = 8080
allowed_origins = ["https://app.example.com", "https://admin.example.com"]

[database]
url = "postgres://db:5432/shop"

[logging]
level = "warn"
format = "json"

Where YAML diverges: indentation as structure

YAML drops the [headings] and brackets entirely and uses indentation to carry the nesting — the same information, with roughly a third fewer punctuation characters, and comments allowed anywhere. That readability is also YAML's best-known failure mode: two spaces where a list item needed four silently reattaches a key to the wrong parent, and nothing about the file LOOKS wrong until something downstream reads a value that isn't there.

The same JSON rendering (below, in the types section rather than repeated here) is denser than either: braces and quotes around every key, no comments, and a trailing-comma trap that trips up hand edits. That combination — machine-precise, human-hostile — is exactly why JSON rarely gets used as a config format a person edits directly; it's the format a PROGRAM writes.

yaml
server:
  port: 8080
  allowed_origins:
    - https://app.example.com
    - https://admin.example.com
database:
  url: postgres://db:5432/shop
logging:
  level: warn
  format: json

Comments — and why JSON lost configuration files

TOML and YAML both support # comments anywhere in the file — the annotation that makes a config file self-documenting ("# staging only, remove before prod") instead of relying on a separate README or a commit message nobody will read again. JSON, per RFC 8259, has no comment syntax at all: a # or // in a .json file is a parse error, full stop, which is the single biggest reason JSON lost the config-file role it briefly held in the mid-2010s (npm's package.json being the enduring exception, kept for historical and tooling-compatibility reasons rather than because JSON is pleasant to hand-edit).

Where JSON-like syntax with comments is genuinely wanted, the answer isn't ad-hoc — it's JSON5 or JSONC (JSON with Comments, the dialect VS Code's own settings.json uses), both supersets of JSON that add comments and a few other conveniences while staying close enough to parse with a relaxed JSON parser. This site's JSON5 Formatter handles exactly that dialect if you're maintaining a JSONC-style file rather than migrating away from JSON entirely.

Types: TOML's first-class dates, YAML's implicit typing, JSON's six values

JSON's type system is the shortest to describe because it's exhaustive: string, number, boolean, null, object, array — six kinds, no more, no native date type, and no integer/float distinction beyond what the value itself looks like. TOML goes the other direction, with the richest built-in type set of the three: strings (four different quoting styles for different escaping needs), 64-bit integers, floats, booleans, and — uniquely — four native date/time types (offset date-time, local date-time, local date, local time), each with its own unambiguous syntax. There is no TOML null; a key either has a value or doesn't exist.

YAML's type system is the one that causes production incidents, because it's IMPLICIT: a bare scalar's type is inferred from its text by a set of resolution rules, and those rules changed between YAML 1.1 and 1.2. The canonical trap — nicknamed the Norway Problem — is a country code of NO: under YAML 1.1's Core Schema (what older parsers like PyYAML's default loader and SnakeYAML implement), the bare word NO resolves to the boolean false, not the string "NO". YAML 1.2 narrowed the set of words that resolve to booleans (only true/false survive unquoted), which fixes the Norway case but means the SAME FILE can parse differently depending on which YAML version reads it — the honest fix, in any version, is to quote a value whenever its bare form could be misread.

Native types by format
TypeTOMLYAML (1.2 core schema)JSON
StringYes — 4 stylesYes, implicit unless quotedYes
IntegerYes, 64-bitYes, implicitNumber only (no int/float split)
FloatYes, incl. inf/nanYes, implicitNumber only
Booleantrue / false onlytrue/false (1.1 also yes/no/on/off)true / false
nullNot representablenull or ~null
Date / time4 native kindsNot native — a string unless quoted or a loader extensionNot native — always a string
BinaryNoNo (base64 as a string, by convention)No

Nesting and repetition: tables, indentation, braces

All three represent nested structure and repeated structure, but the ergonomics diverge sharply once a list of objects shows up. TOML's array-of-tables syntax ([[bin]], repeated once per element) reads well for a handful of similarly-shaped records — see the example in the TOML to JSON page — but gets unwieldy past a dozen or so, at which point an inline array of inline tables is usually more compact. YAML's dash-prefixed list items nest naturally under any key at any depth and stay readable even heavily nested, which is exactly why Kubernetes manifests — deeply nested container specs, volume mounts, environment variable lists — are YAML and not TOML. JSON's braces and brackets nest arbitrarily deep with zero ambiguity, at the cost of density; a JSON file five levels deep is a wall of closing braces that YAML's indentation makes visually obvious for free.

YAML alone offers anchors (&name) and aliases (*name) — a way to define a block once and reference it elsewhere without repeating it, which is genuinely useful for Docker Compose service defaults or CI job templates that share most of their configuration. Neither TOML nor JSON has an equivalent; converting FROM YAML to either format always resolves anchors into their full expanded content, because there's nowhere else for the reference to go.

Who standardized on what

The format a tool chose is rarely arbitrary — it usually reflects exactly the trade-offs above: does a human write this file by hand, does it need comments, does it nest deeply, does the ecosystem already have strong tooling in one format.

Config format by ecosystem
FileFormatWhy (roughly)
pyproject.tomlTOMLPEP 518 chose TOML explicitly for Python packaging metadata — see the FAQ below
Cargo.tomlTOMLRust's package manifest; flat-ish, hand-edited, fixed schema
wrangler.tomlTOMLCloudflare Workers configuration
netlify.tomlTOMLNetlify build/deploy configuration
Hugo site configTOML (or YAML/JSON)Hugo accepts all three; TOML is the historical default
Kubernetes manifestsYAMLDeep nesting, hand-maintained, comments valued
GitHub Actions workflowsYAMLNested job/step structure, readability under version control
Docker Compose filesYAMLAnchors for shared service defaults, deep nesting
Ansible playbooksYAMLHuman-authored, list-heavy, comment-friendly
OpenAPI specsYAML or JSONBoth accepted; YAML preferred for hand-authored specs
package.json, tsconfig.jsonJSONWritten and read by tooling far more often than by a person
ESLint flat configJavaScript (not JSON/YAML/TOML)Modern ESLint moved to a real JS module for full expressiveness
.vscode/settings.jsonJSONC (JSON with comments)VS Code's own settings accept comments despite the .json extension
GemfileRuby DSL (not YAML)Ruby's own Bundler format is executable Ruby, not a data format

INI and .properties: the older cousins

Before TOML formalized the idea, plain INI files (section headers in [brackets], flat key=value pairs, no real nesting, no standardized spec) covered similar ground with far less precision — no defined types, no defined nesting, and every parser implementing its own dialect. TOML is best understood as "INI, but with an actual specification, real types, and a defined way to nest." Java's .properties format is the other flat-file cousin: strictly key=value, no sections at all, nesting simulated by convention through dotted keys (server.port=8080), and Spring Boot's own YAML support exists specifically because .properties' flatness becomes painful past a few dozen keys.

Converting between YAML's real nesting and .properties' dotted-key convention is common enough in the Spring Boot world that it's its own pair of tools here: YAML to Properties flattens a nested application.yml into dotted keys, and Properties to YAML reverses it, rebuilding real nesting (and even keeping your # comments, which the TOML and JSON converters below cannot do, since neither format's grammar attaches comments to the parsed value the way this dedicated flattening logic can).

A decision guide

None of these formats is objectively better — each optimizes for a different reader. The table below is the practical version of everything above.

Choose TOML / YAML / JSON when…
Choose…When…The failure mode you accept
TOMLA human edits a flat-to-moderately-nested config with a fixed schema, and unambiguous typing matters (versions, dates, package metadata)Deep nesting gets verbose; no comments-with-reserialization if a tool ever rewrites the file
YAMLThe config is deeply nested, hand-maintained, comments matter, and you can enforce quoting discipline (or a linter) against implicit typingThe Norway Problem and indentation-as-syntax bugs, if discipline slips
JSONA program writes and/or reads the file — an API response, a lockfile, a build artifact — and a human rarely edits it directlyNo comments, no trailing commas, verbose for a human to hand-maintain

Converting between them without losing meaning

Every conversion between these formats is lossy in a specific, predictable way, and knowing which loss applies to your direction is the difference between a confident migration and a surprised bug report. YAML → anything loses comments (nothing else has them) and resolves anchors/aliases into their full expanded content. Anything → TOML loses null (there's nothing to convert it to, so it's omitted or rejected, your choice — see JSON to TOML). TOML → JSON keeps everything except the format of dates (which become plain ISO strings) and needs a string fallback for 64-bit integers past what a JSON number can hold exactly (see TOML to JSON).

The tools on this site that make these specific conversions: TOML to JSON and JSON to TOML for the TOML/JSON pair, TOML Formatter to validate or normalize a TOML file on its own, and YAML to Properties / Properties to YAML for the Spring Boot–style flattening pair discussed above. For the YAML/JSON pair specifically — not covered by this guide in depth since it's the more established of the three — see JSON to YAML, YAML to JSON, and the general YAML vs JSON comparison.

How this was tested: TOML examples were parsed with smol-toml 1.8, YAML with the yaml package 2.9, and JSON with JSON.parse — the same engines this site's converters use — on 2026-09-18.

Sources

  1. TOML v1.0.0 specification
  2. YAML 1.2.2 specification
  3. RFC 8259 — The JSON Data Interchange Format
  4. PEP 518 — Specifying Minimum Build System Requirements for Python Projects
  5. The Cargo Book — Manifest Format

Frequently asked questions

Is TOML a superset or subset of JSON?

Neither, exactly — they're siblings that both aim at the same underlying data model (a tree of strings, numbers, booleans, arrays and key-value tables) through different syntax, and neither's SYNTAX is valid in the other. In terms of what each can EXPRESS: every TOML document maps onto an equivalent JSON value (with dates flattened to strings and null simply absent, since TOML has none), but the reverse doesn't hold — a JSON document containing null, or a top-level array rather than an object, has no direct TOML equivalent, which is exactly the constraint the [JSON to TOML](/json-to-toml) converter on this site has to work around.

Why did Python choose TOML for pyproject.toml?

PEP 518, which introduced pyproject.toml, states TOML's advantages directly: it's a fully specified, easily human-editable format designed specifically for configuration (unlike JSON, which PEP 518 notes is "not fun to write by hand" and has no comments), and it's simpler and more constrained than YAML, avoiding YAML's implicit-typing ambiguity and the security concerns some YAML loaders have historically carried. The PEP also weighed inventing a new Python-specific format and rejected it in favor of an existing, already-specified one — TOML was, at the time, the closest fit to "INI file with real types and a spec."

Can TOML represent null?

No — there's no null keyword or equivalent anywhere in the TOML 1.0 grammar; a table either has a key with a real value, or the key simply isn't present. This is a deliberate simplicity choice, not an oversight, and it's the reason [JSON to TOML](/json-to-toml) on this site has to make an explicit decision about every null value it encounters: omit the key (the default, with a count reported) or fail the conversion outright so you notice the null before it silently disappears.

Which of the three parses fastest?

JSON, by a wide margin — its grammar is small, unambiguous, and every mainstream language ships a highly optimized native parser (V8's JSON.parse is close to hand-written-C speed). TOML parses close behind JSON, since its grammar is similarly compact even with the richer type set. YAML is reliably the slowest of the three and the most complex to implement correctly — full YAML 1.2 support means handling anchors, multiple document streams, several scalar styles, and a much larger grammar overall. In practice this almost never matters for a config file read once at startup; it matters far more for a format used as a wire protocol for many small, frequent messages, which is a large part of why JSON (not YAML) is what APIs actually speak.

Does TOML have multi-line strings and comments?

Yes to both. Comments start with # and run to the end of the line, exactly like YAML and Python. Multi-line strings come in two flavors: """triple double-quoted""" strings process escape sequences the same as a regular basic string (and a leading newline right after the opening """ is trimmed, a convenience for formatting), while '''triple single-quoted''' literal strings take their content completely as-is with zero escaping, which is the natural choice for a regex pattern or a Windows path that's full of backslashes you don't want interpreted.

Paste a TOML file and get clean JSON in your browser — dates, 64-bit integers and arrays of tables all handled correctly.

Convert TOML now

Keep working

Related tools