JSON, YAML, TOML or XML? Choosing a config format and converting between them
Every project eventually has a file that nobody wants to own: the one holding the timeouts, the feature flags, the list of environments. The format that file is written in decides how often it breaks. JSON cannot carry a comment. YAML will decide, without asking, that your Norwegian country code is the boolean false. TOML refuses to nest more than about two levels before turning ugly. XML is verbose in a way that is either bureaucratic or exactly right, depending on what you are storing. This guide covers what each format is genuinely good at, the failure modes worth knowing before you commit to one, and — because converting between them is rarely lossless — precisely what the JSON to YAML, YAML to JSON, JSON to TOML, JSON to XML and XML to JSON converters here do with the awkward cases. Every example below was run through the exact parsers those pages use.
The four formats at a glance
| JSON | YAML | TOML | XML | |
|---|---|---|---|---|
| Comments | No | Yes (#) | Yes (#) | Yes (<!-- -->) |
| Deep nesting | Fine | Fine | Painful past two levels | Fine |
| Null | null | null, ~, empty | No such thing | Convention only (empty or an attribute) |
| Type surprises | Almost none | Many | Few | Everything is text until a schema says otherwise |
| Whitespace matters | No | Yes — it is the syntax | No | Only inside elements |
| Schema story | JSON Schema | JSON Schema (via the parsed value) | Rare | XSD, DTD, RELAX NG — mature |
| Best at | Machine-to-machine | Deep, hand-edited config | Flat, hand-edited config | Documents and interchange contracts |
JSON: boring on purpose
JSON’s virtue is that there is almost nothing to argue about. Six types (object, array, string, number, boolean, null), no comments, no trailing commas, no dates, no integers-versus-floats distinction. Every language parses it the same way, which is why it won as a wire format. As a config format its omissions bite: you cannot leave a note explaining why a timeout is 37 seconds, and adding a line to a list produces a two-line diff because the previous line needs a comma.
Two JSON details cause real bugs. First, the specification puts no limit on number precision, but almost every parser uses IEEE 754 doubles — so an ID beyond 253 is silently rounded, and a 64-bit database key should travel as a string. Second, key order is not meaningful; some parsers preserve it, none promise to, and a diff-friendly file therefore needs a deliberate sort.
The converters on this site read JSON with JSON5 rather than JSON.parse, which is a convenience at the input end: you can paste config that has comments, trailing commas, unquoted keys or single-quoted strings and it will still convert. What comes out is standard JSON, and the comments are gone — JSON5 is a forgiving door, not a format you should ship.
YAML: readable until it guesses
YAML is what you get when you ask “what if config looked like an outline?” — no braces, no quotes, indentation as structure. It is genuinely nicer for a hundred-line Kubernetes manifest. The cost is that YAML tries very hard to work out what you meant, and a config file is exactly where guessing is expensive.
Here is a file of plausible-looking values, and what the parser behind the YAML to JSON converter actually produces:
country: no
version: 1.10
zip: 08540
octal: 0o14
time: 12:30
big: 12345678901234567890
tilde: ~
empty:{
"country": "no",
"version": 1.1,
"zip": 8540,
"octal": 12,
"time": "12:30",
"big": 12345678901234567000,
"tilde": null,
"empty": null
}Read that output slowly, because four separate classes of damage are visible in eight lines:
- The version lost its trailing zero.
1.10parsed as a number is 1.1. Semantic versions, ZIP codes, phone numbers, part numbers and any identifier that merely resembles a number must be quoted. - The ZIP code lost its leading zero for the same reason —
08540is the number 8540, and the Massachusetts and New Jersey address book is full of these. - The big integer lost precision.
12345678901234567890came back as...567000: past 253 a JavaScript number cannot represent every integer, and YAML gives you no warning. - An empty value is null, not an empty string.
empty:andtilde: ~are bothnull. A config loader that expects a string gets one of the more annoying runtime errors there is.
And then the famous one. country: no came back as the string“no” here, because this parser implements YAML 1.2, whose core schema recognises only true and false as booleans. Under YAML 1.1 — which PyYAML and a great many Go and Ruby libraries still implement — no, n, off and y are booleans, and Norway becomes false. This is the worst kind of bug: the file is valid everywhere and means different things in different places. Quote the string and the question never arises.
Three more YAML features are worth recognising when you meet them in someone else’s file:
- Anchors and aliases.
&namedefines a node,*namereuses it,<<: *namemerges it into a map. Converting YAML to JSON expands them, so the output is bigger and the shared-value intent is gone. Going the other way, the converter emits an anchor whenever two keys point at the same object in memory. - Multiple documents.
---separates documents in one file — standard practice for Kubernetes. JSON has no equivalent, so a multi-document file needs splitting before conversion; the converter here reads a single document. - Block scalars.
|keeps newlines,>folds them into spaces, and a trailing-strips the final newline. This is how scripts get embedded in CI config, and it is the one place YAML is unambiguously better than JSON.
Going from JSON to YAML is the safe direction: the converter quotes anything that would otherwise be re-read as a number or boolean, so a JSON string "yes" comes out as 'yes' rather than a naked yes. Long strings get folded onto continuation lines with >-, which is cosmetic but surprises people diffing the result.
TOML: flat files, obvious meaning
TOMLexists because YAML is too clever and INI is too vague. It has explicit types, real dates, and section headers that read like what they are. Rust’s Cargo, Python’s pyproject.toml, Netlify and Hugo all use it, and for a mostly-flat file it is the format least likely to surprise you: there is no type guessing, so version = "1.10" is a string and version = 1.10 is a float, and you chose which.
Two behaviours matter when converting into it:
- TOML has no null. A key whose JSON value is
nullis simply dropped from the output — the value does not become an empty string or a zero, the key ceases to exist. If null is meaningful in your data, TOML is the wrong target. - Order is rearranged. TOML requires every top-level scalar to appear before the first table header, so the converter emits all the plain keys first and the
[section]blocks after, whatever order they had in the JSON. An array of objects becomes an array of tables — repeated[[rows]]headers — which is TOML at its most readable.
One more thing about the JSON to TOML page specifically: if you paste a top-level JSON array, it converts the first element only. TOML documents are tables, so there is nothing sensible to do with a bare list at the root; the page takes the first record as the sample, which is what you want when you are sketching a config from an API response and not what you want if you expected all 500 rows.
Deep nesting is where TOML stops being pleasant. This is fine:
[database]
host = "db.internal"
port = 5432And this, three levels down, is why teams reach back for YAML: [servers.eu.west.primary], repeated for every leaf, with the shared prefix retyped every time. If your config is a tree, TOML will make you feel it.
XML: still the right answer for documents
XML lost the config-file argument and won the document one. Anything with mixed content — text with markup insideit, like a paragraph containing a link — is natural in XML and awkward in all three of the others. It also has the most mature schema and validation story: XSD can express “this element repeats between 1 and 10 times and its idattribute must be unique”, and every enterprise integration that has to be checkable at the boundary still leans on that. Office documents, EPUB, SVG, RSS, SOAP and most government interchange formats are XML for these reasons and are not moving.
The awkwardness in converting XML to JSON is not verbosity, it is that XML has two ways to hold a value and JSON has one. Parsing this:
<r><a id="1">x</a><a>y</a><b>007</b><c>true</c></r>with the XML to JSON converter gives:
{
"r": {
"a": [ { "#text": "x", "@_id": "1" }, "y" ],
"b": 7,
"c": true
}
}Four conventions are on display, and all four are choices rather than facts about XML:
- Attributes become keys prefixed with
@_, soid="1"is"@_id". - An element with both text and attributes puts its text under
#text; one with only text becomes a plain string. So the two<a>elements come back in two different shapes inside the same array. - Repetition decides array-ness. One
<item>parses to a string; two parse to an array. A document that happens to contain a single row this time produces a different JSON shapefrom the same document with two rows — the single most common cause of “it worked in testing” in XML-to-JSON pipelines. Anything downstream must normalise to an array before iterating. - Values are coerced:
007became the number 7 andtruebecame a boolean. Zero-padded reference numbers do not survive.
The reverse direction is weaker still, and it is worth being blunt about it. XML has no array type, so the JSON to XML converter emits every element of a list inside one wrapping element: {"items":[{"id":1},{"id":2}]} becomes <items><id>1</id><id>2</id></items> — the record boundary is gone, and no parser can put it back. Use that page to sketch a shape, then decide the repeated element name yourself; do not use it as a mechanical bridge for list data.
What conversion actually loses
Every conversion is a round trip through a data model, and each format’s model is missing something the others have. This is the table to check before converting anything you cannot re-generate:
| Conversion | What is lost |
|---|---|
| YAML → JSON | Comments, anchors (expanded), block-scalar style, multi-document structure, quoting intent |
| JSON → YAML | Nothing structural; strings gain quotes and long lines get folded |
| JSON → TOML | Nulls (keys dropped), key order, and every array element after the first if the root is an array |
| XML → JSON | Attribute/element distinction (flattened to a prefix), single-vs-repeated shape stability, namespaces, comments, ordering of mixed content |
| JSON → XML | Array boundaries, types, and any notion of which fields should have been attributes |
All five pages work the same way: paste or Load File on the left, output on the right, Copy or Download when it looks right. If the input cannot be parsed the output pane shows a comment instead of a result — //Invalid YAML, or //Invalid XML: <the validator’s own message>, which usually names the offending tag. Nothing is uploaded; the parsing happens in the page, which matters because config files are full of hostnames, bucket names and occasionally a secret that should not have been there.
Choosing, in one paragraph
If a machine writes the file more often than a person does, use JSON. If a person edits it and the structure is mostly flat, use TOML. If the ecosystem has already chosen YAML for you, use YAML and quote aggressively. If the file is a document, or a third party will validate it against a schema, use XML. And whichever you pick, put it in version control and give it a schema or an example file, because the format was never the actual problem — the undocumented key that only one person understands was.
Do this
- Quote every YAML value that could be read as something else:
"1.10","08540","no","12:30". - Send 64-bit IDs as strings in JSON and YAML alike — past 2^53 the parser rounds them and says nothing.
- Do not convert JSON containing nulls to TOML: those keys silently disappear.
- After XML to JSON, normalise every repeatable element to an array before iterating — one child parses to a string, two to a list.
- Treat JSON to XML output as a sketch, not a bridge: array boundaries do not survive it.
- Keep comments out of the JSON you ship; if the file needs comments, that is the argument for YAML or TOML.
Frequently asked questions
Which config format should I pick for a new project?
TOML if a human will edit it by hand and the structure is mostly flat — Cargo, pyproject and Netlify all landed there for that reason. YAML if the ecosystem already expects it (Kubernetes, GitHub Actions, Docker Compose) or the file is genuinely deep. JSON if a program writes it more often than a person does. XML only when you need documents, mixed content or a schema that a third party will validate against.
Why does YAML turn my version number 1.10 into 1.1?
Because unquoted 1.10 is a number, and 1.10 and 1.1 are the same number. The trailing zero is not data, it is formatting, and it disappears the moment the value is parsed. Any value that only looks like a number — versions, ZIP codes, phone numbers, git SHAs that happen to be all digits — has to be quoted.
Is the “Norway problem” still real?
It depends on the parser version, which is what makes it dangerous. YAML 1.1 parsers (PyYAML, many Go and Ruby libraries) read no, off and n as false. YAML 1.2 parsers — including the js-yaml build behind the converters here — leave them as the strings "no", "off" and "n". The same file therefore means different things in different tools, so quote every string that could be read as a boolean.
Why does my JSON to XML output lose the boundaries between array items?
The converter serialises an array by emitting each element inside one wrapping element, so a list of objects collapses into a flat run of children. XML has no array type — the shape has to be chosen by hand, usually as a repeated element name. Convert lists deliberately rather than expecting a mechanical round trip.
Can I put comments in JSON?
Not in standard JSON, and no parser you send it to is obliged to accept them. The converters on this site read input with JSON5, so pasted comments, trailing commas and unquoted keys are tolerated at the input end — but the JSON they emit is plain RFC 8259 JSON with the comments gone. If you need commented config, that is an argument for YAML or TOML rather than for a JSON dialect.
Tools used in this guide
Every one of these runs in your browser — the files you work on never leave your device.