Use JSON for data exchanged between programs, TOML for configuration files that people edit by hand, and YAML when a tool you depend on already expects it (GitHub Actions workflows, for example, must be YAML). JSON has no comments and no trailing commas, YAML has implicit typing that can turn NO into false, and TOML gets awkward when data is deeply nested.
Below is the same configuration in all three formats, followed by the mistakes each one invites. Every example was loaded with Python 3.12 (json, tomllib, and PyYAML 6.0.1) to confirm what the parsers actually return.
The same config in JSON, YAML and TOML
JSON:
{
"name": "shop-api",
"port": 8080,
"debug": false,
"allowed_hosts": ["example.com", "api.example.com"],
"database": {
"host": "db.internal",
"port": 5432
}
}
YAML:
# Service settings
name: shop-api
port: 8080
debug: false
allowed_hosts:
- example.com
- api.example.com
database:
host: db.internal
port: 5432
TOML:
# Service settings
name = "shop-api"
port = 8080
debug = false
allowed_hosts = ["example.com", "api.example.com"]
[database]
host = "db.internal"
port = 5432
All three load to an identical Python dictionary; comparing the parsed results with == returns True. Only YAML and TOML could carry the # comment. To convert JSON arrays to a spreadsheet-friendly table, the JSON to CSV converter runs in the browser.
At a glance
| JSON | YAML | TOML | |
|---|---|---|---|
| Specification | RFC 8259 | YAML 1.2.2 (2021) | TOML 1.0.0 |
| Comments | No | # | # |
| Structure from | Braces and brackets | Indentation | [table] headers |
| Strings need quotes | Always, double quotes | Usually not | Always |
| Types guessed from bare text | No | Yes | No |
| Native date/time | No | Parser-dependent | Yes (RFC 3339) |
| Deep nesting | Fine | Fine | Verbose |
| Python stdlib reader | json | None (needs PyYAML or similar) | tomllib (3.11+, read-only) |
JSON gotchas: no comments, no trailing commas, double quotes only
RFC 8259 defines no comment syntax at all, and its grammar allows a comma only between values, so a trailing comma is invalid. Strings must start and end with "quotation marks", meaning the double quote character. The real errors:
>>> json.loads('{"a": 1,}')
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 9 (char 8)
> JSON.parse('{"a":1,}') // Node.js 18
SyntaxError: Unexpected token } in JSON at position 7
A config written with single quotes, {'debug': true}, fails the same way in Python. Two smaller points from the RFC: names within an object "SHOULD be unique", which means duplicates are not forbidden but parsers handle them unpredictably, and JSON exchanged between systems "MUST be encoded using UTF-8".
JSON's strictness is also its strength: there is exactly one way to write each value, every language has a parser, and nothing is guessed. That makes it the right choice for API payloads, lock files and anything a program writes.
YAML gotchas: the Norway problem and implicit typing
YAML lets you leave strings unquoted, so the parser has to guess the type of every bare value. Under YAML 1.1 rules, which PyYAML still follows, those guesses go wrong in ways that are easy to miss. This input:
country: NO
version: 3.10
zip: 012
on: yes
time: 12:30
loads in PyYAML 6.0.1 as:
{'country': False, 'version': 3.1, 'zip': 10, True: True, 'time': 750}
NO(the country code for Norway) becameFalse. YAML 1.1's boolean type matchesy|Y|yes|Yes|YES|n|N|no|No|NO|true|...|on|On|ON|off|Off|OFF.3.10became the float3.1, which is how a Python version silently turns into the wrong one in CI files.012was read as octal and became10.- The key
onbecame the booleanTrue. 12:30was read as a base-60 number: 12 × 60 + 30 = 750.
YAML 1.2 removed most of this. Its core schema only treats true, True, TRUE, false, False and FALSE as booleans and writes octal as 0o14. But your values are interpreted by whichever parser the tool uses, not by the latest spec, so the safe habit is to quote anything that is meant to be a string:
country: "NO"
version: "3.10"
which loads as {'country': 'NO', 'version': '3.10'}.
YAML gotchas: indentation and tabs
Indentation defines structure, so a list item indented one space too far ends up under a different key without any error. Tabs are not allowed at all; the spec says "tab characters must not be used in indentation". A tab produces:
yaml.scanner.ScannerError: while scanning for the next token
found character '\t' that cannot start any token
Set your editor to insert spaces in .yaml files. When two YAML files behave differently and you can't see why, compare them in a diff checker; whitespace differences show up there that are invisible in an editor.
TOML gotchas: tables, duplicate keys and quoting
TOML is strict where YAML is loose. Strings must be quoted, booleans are only lowercase true and false, and indentation "is treated as whitespace and ignored". The errors you will meet come from structure instead:
name = shop → TOMLDecodeError: Invalid value (at line 1, column 8)
a = 1 / a = 2 → TOMLDecodeError: Cannot overwrite a value
[db] twice → TOMLDecodeError: Cannot declare ('db',) twice (at line 3, column 4)
Each key can be defined only once and each [table] header only once. Nested tables use dotted headers, and lists of tables use double brackets:
[server.tls]
enabled = true
[[users]]
name = "a"
[[users]]
name = "b"
That loads as {'server': {'tls': {'enabled': True}}, 'users': [{'name': 'a'}, {'name': 'b'}]}. It reads well two levels deep and becomes tedious at four or five, which is where JSON or YAML are easier to follow. One thing TOML gets right that the others don't: d = 1979-05-27T07:32:00Z is a real date-time value, returned by tomllib as a timezone-aware datetime.
Note that v = 3.10 in TOML is still a float and loads as 3.1, like in YAML. TOML doesn't guess types, but a number is a number; write "3.10" if you mean a version string.
Which format to choose
- JSON: anything machine-written or sent over the network, such as API requests and responses, package lock files, and data exports. Use it when no human edits the file, or when the consumer is JavaScript.
- TOML: hand-edited application config with a shallow structure. Python projects keep tool settings in
pyproject.toml, which the Python Packaging User Guide describes as "a configuration file used by packaging tools, as well as other tools such as linters, type checkers, etc." Clear errors and no type guessing make mistakes visible. - YAML: when the tool has already chosen it. GitHub's docs, for example, state that "Workflow files use YAML syntax". Quote strings, use spaces, and run a linter.
If a tool accepts more than one format, prefer the one that fails loudly over the one that guesses. When an API rejects your JSON, the HTTP status tells you whether the problem is the syntax (usually 400) or the content (often 422); see HTTP status codes explained.
Frequently asked questions
- Can JSON have comments?
- Not in standard JSON. RFC 8259's grammar has no comment syntax, so strict parsers such as Python's json module and JavaScript's JSON.parse reject them. Some tools accept a relaxed variant (often called JSONC) for their own config files, but that file is then no longer portable JSON.
- Is YAML a superset of JSON?
- YAML 1.2 was designed to be one: the specification says its primary focus was making YAML a strict superset of JSON. In practice most JSON documents load as YAML, so a YAML parser can usually read a JSON config file unchanged.
- Why does YAML turn NO into false?
- YAML 1.1 treated y, n, yes, no, on and off (in several capitalisations) as booleans. YAML 1.2's core schema only recognises true and false, but many widely used parsers, including PyYAML, still follow 1.1 rules. Quote any value that must stay a string, for example country: "NO".
- Which format should I use for a Python project's configuration?
- TOML. Python packaging tools read project metadata from pyproject.toml, and Python 3.11 and later include the tomllib module for reading TOML without third-party packages.
Sources
- RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format
- YAML Ain't Markup Language version 1.2.2 specification
- YAML 1.1 Boolean Language-Independent Type
- TOML v1.0.0 specification
- GitHub Docs: Workflow syntax for GitHub Actions
- Python documentation: tomllib
- Python Packaging User Guide: Writing your pyproject.toml