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

  • tomllib is read-only and stdlib in 3.11+; on 3.10 use tomli. To write TOML, use tomli-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 tomllib cutover.
  • 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.