TOML vs YAML vs JSON for Python config
Three formats dominate Python configuration files, and each fails differently: JSON has no comments, YAML has implicit-typing footguns and an unsafe loader, and TOML is verbose for deep nesting. This page picks the right one per job. It extends YAML & JSON Parsing Strategies.
Problem 1: YAML implicit typing
# ANTI-PATTERN: values silently retyped
version: 3.10 # -> float 3.1
enabled: on # -> True
country: no # -> False
YAML 1.1 reinterprets these scalars; quote them or validate types explicitly.
Problem 2: JSON with no comments and trailing-comma errors
{
"port": 8080,
"debug": false,
}
That trailing comma is a parse error, and JSON has no way to document a field inline.
Secure implementation
# config/format_loader.py
import json
import tomllib # stdlib, Python 3.11+
from pathlib import Path
import yaml
from pydantic import BaseModel
class Config(BaseModel):
model_config = {"extra": "forbid"}
port: int = 8080
debug: bool = False
def load(path: Path) -> Config:
text = path.read_text()
match path.suffix:
case ".toml":
raw = tomllib.loads(text) # typed, comments, no surprises
case ".json":
raw = json.loads(text) # machine-friendly, strict
case ".yaml" | ".yml":
raw = yaml.safe_load(text) # safe_load ONLY
case _:
raise ValueError(f"unsupported: {path.suffix}")
return Config.model_validate(raw) # validate regardless of format
Whatever the format, the parsed dict is validated by the same pydantic model — so the format is a readability choice, not a correctness risk.
Comparison
| Format | Comments | Typing | Loader | Best for |
|---|---|---|---|---|
| TOML | yes | explicit | tomllib |
hand-edited app config (pyproject.toml) |
| YAML | yes | implicit (risky) | yaml.safe_load |
deep nesting, anchors |
| JSON | no | explicit | json.loads |
machine-generated config / APIs |
Gotchas & version-specific behaviour
tomllibis read-only and stdlib in 3.11+; on 3.10 usetomli. To write TOML, usetomli-w.- YAML must use
safe_load; quote ambiguous scalars like"3.10"and"on". - JSON cannot carry comments — keep a sibling docs file or switch to TOML.
- Validate every format with
extra="forbid"so typo’d keys fail.
Production parity checklist
- One pydantic model validates the parsed config regardless of format.
- YAML uses
safe_load; ambiguous scalars are quoted. - TOML loading accounts for the 3.11
tomllibcutover. extra="forbid"rejects unknown keys.- Secrets are referenced from a store, never embedded in the file.
Conclusion
Pick TOML for hand-edited config, JSON for machine output, YAML for deep nesting — then validate all three through one model so the choice is purely ergonomic. For nested-structure handling, see Handling Nested Configuration in YAML Safely.