JSON, YAML or TOML: choosing a config format

Converting between JSON, YAML and TOML looks like a formatting exercise until a value changes type, a comment disappears, or a key ordering silently rearranges your config. The formats are not equivalent representations of the same thing.

What each format is actually optimised for

FormatStrengthWeaknessTypical use
JSONUnambiguous, universally supported, strictNo comments, no trailing commas, verboseAPIs, data interchange, machine-to-machine
YAMLReadable, comments, anchors for reuseType inference surprises, whitespace-sensitiveHuman-edited config, CI pipelines
TOMLExplicit types, comments, obvious structureAwkward for deeply nested dataApplication and package config

They overlap enough to look interchangeable, and the overlap is where the bugs are.

YAML's type inference is the famous trap — and it depends on the version

YAML infers types from how a value looks, and the two major versions infer differently. This is the single most confusing thing about YAML config, because the same file can parse two ways in two programs. The table below is the actual behaviour of the parser on this site, which follows YAML 1.2:

WrittenYAML 1.2 (this site)YAML 1.1 (older parsers)
country: NO"NO" — a stringfalse — a boolean
version: 1.101.1 — a number1.1 — a number
perm: 0755755 — decimal493 — octal
flag: on"on" — a stringtrue — a boolean
ratio: 1:30"1:30" — a string90 — sexagesimal

So the notorious “NO became false” outage is a YAML 1.1 problem. If your parser is 1.1 (older PyYAML, some Ruby and Java libraries, many CI systems), a country code, a two-letter state abbreviation, or a product code written unquoted silently becomes a boolean. Under 1.2 it stays a string — which is why a config can work locally and break in CI.

The durable rule is unaffected by which version you are on: quote anything that must be a string. Version-specific inference is a trap you avoid by not relying on it.

country: "NO"        # always a string, both versions
perm: "0755"         # always a string; parse it yourself if you need octal
version: "1.10"      # keeps the trailing zero, which a number would drop

Note version: 1.10 in the table: even under 1.2 it becomes the number 1.1, losing the trailing zero. If the value is a version, it is a string, and it needs quotes.

Octal is its own trap

Under 1.2, 0755 is the decimal number 755 — not the octal permission 493. That is arguably a better default, but it means a file-mode value written unquoted is wrong in a way that looks right in the text. Write file modes as strings ("0755") and convert explicitly.

What a conversion loses

From → ToWhat survivesWhat does not
JSON → YAMLAll data, typesNothing structurally — but the output may become implicitly typed if re-read carelessly
YAML → JSONData, typesComments, anchors/aliases (expanded), merge keys, ordering guarantees
JSON → TOMLData, typesNull (TOML has no null), top-level arrays of mixed types
TOML → JSONData, typesComments, datetime becomes a string
XML → JSONElements and textAttributes, namespaces, mixed content, ordering, comments

Those losses are the reason “convert then convert back” is not a safe migration strategy. Each arrow is a real translation with lossy corners, not a re-indentation.

The null problem in TOML

TOML has no null value. A JSON object with "timeout": null has no direct TOML representation, so a converter must either omit the key or write an empty string — and those mean different things when read back. An omitted key usually means “use the default”; an empty string means “explicitly empty”. If your config distinguishes them, TOML cannot carry that distinction without a convention.

Numbers: precision and representation

JSON numbers are parsed by some languages as floats, so a 64-bit integer id can lose precision above 253. YAML and TOML have the same issue depending on the parser. If an id must survive, keep it a string in every representation — this is why large systems return ids as strings.

The same applies to decimals in money. 1.10 is not representable exactly in binary floating point; storing it as a number invites a rounding drift that appears only after arithmetic. Store cents as an integer, or the amount as a string.

Comments and ordering

Neither JSON nor its object model has comments, and neither JSON nor TOML preserves key order as meaningful data. YAML does preserve order in practice and supports comments. If your config relies on comments to explain non-obvious choices, converting to JSON for a tool and back will delete them. That is not a bug in the converter; it is the format being asked to carry something it does not have.

Practical guidance

  1. Use JSON between machines. It is strict, which is a feature when the producer and consumer are different programs.
  2. Use YAML only for files humans edit, and quote every string that could be misread. Prefer a schema validator to catch the rest.
  3. Use TOML for application config with shallow nesting. It makes types explicit and keeps comments.
  4. Never round-trip as a migration. Pick the target format once, convert, and then review the diff — do not assume the reverse conversion reproduces the original.
  5. Keep ids and money as strings in every format, always.

XML is a different kind of thing

JSON, YAML and TOML are all trees of maps and arrays — converting between them is mostly a formatting exercise with the edge cases above. XML is not: it is a document format with attributes, namespaces, mixed content (text and elements interleaved) and ordered siblings. Mapping it onto a map loses information no matter which convention you pick.

XML featureWhat JSON can holdWhat is lost
AttributesKeys with a naming convention (@id, _id)Nothing, if the convention is consistent
Repeated siblingsAn array — but only if the converter knows they repeatA single occurrence and a repeated one look different
Mixed contentNot representableText interleaved with elements collapses or reorders
NamespacesPrefixes in keys, or droppedThe namespace URI is what disambiguates; prefixes are arbitrary
Comments, CDATANothingDropped
Element orderObject order, if preservedMay or may not survive a round trip

The practical consequence: XML → JSON → XML is not a safe migration for a document format. If the data is genuinely a tree of records (an API response, a config), the conversion is fine. If the XML is a document with mixed content, the conversion will silently reorganise it.

Choosing by where the file lives

SituationFormatReason
HTTP API request or responseJSONEvery client parses it; strictness prevents ambiguity
Hand-edited app configTOML or YAMLComments and readable nesting
CI pipeline definitionYAMLThe ecosystem expects it; quote anything type-ambiguous
Package metadataJSON or TOMLDepends on the ecosystem's convention
Document interchange (office, publishing)XMLIt carries structure and mixed content the others cannot
Large analytical datasetCSV or ParquetNot a nested format at all; columnar wins at scale

Validation is not the same as parsing

A parser tells you the document is syntactically well-formed. It does not tell you the data is correct. A YAML file with port: "eighty" parses fine and fails at use. A JSON payload with a missing required field parses fine. Format validation is necessary and never sufficient; if the value matters, validate against a schema (JSON Schema, TOML schema, or an application-level check) before acting on it.

This is also why “it converted without error” is not evidence that a conversion was lossless. Every conversion in the table above will succeed on a document it is silently mangling.

Check it yourself

The data format converter does JSON ↔ YAML ↔ TOML ↔ XML in the browser and shows the converted output directly, so you can see what a real conversion does to your file before committing to it. JSON formatter validates and pretty-prints JSON on its own.

Questions this guide raises

Which format should I use for configuration?

TOML for static configuration a person edits, YAML for Kubernetes and CI where the ecosystem expects it, JSON for APIs and anything a machine produces. The decision is usually made by the tools around the file, not by the syntax: a format nobody else in the stack reads is the wrong choice however pleasant it is.

Does the YAML version really change the meaning?

Yes. YAML 1.1 treats yes, no, on and off as booleans, so the country code NO becomes false; YAML 1.2 core schema only treats true and false as booleans. It also changes how octal-looking values and sexagesimal numbers are read. That is the “Norway problem”, and it is why quoting ambiguous scalars matters.

Why does a round-trip lose my comments?

JSON has no syntax for comments, and YAML/TOML comments are not part of the data model. A converter that goes through JSON has nowhere to put them. Anchors and aliases, key order in some tools, and numeric formatting can also change. Keep the source file and treat conversion as a build step, not as an edit.