Als je een Rust-project hebt geopend, heb je TOML al ontmoet. Als je een Python-pakket hebt aangeraakt dat pyproject.toml gebruikt, heb je het ook gebruikt. TOML — Tom's Obvious Minimal Language — is het configuratiebestandformaat dat stilletjes ontwikkelaars blijft winnen die genoeg hebben van de whitespace-valkuilen van YAML en de weigering van JSON om opmerkingen toe te staan. Gemaakt door Tom Preston-Werner, medeoprichter van GitHub, is TOML ontworpen rond één idee: een configuratieformaat zou zo vanzelfsprekend moeten zijn dat je het kunt lezen zonder een specificatie. Laten we kijken of het die belofte waarmaakt.

Wat Is TOML Precies?

TOML is een configuratiebestandformaat met drie expliciete ontwerpdoelstellingen, die direct bovenaan de officiële specificatie staan: het moet vanzelfsprekend zijn om te lezen, minimaal in complexiteit en ondubbelzinnig mapbaar op een hash-tabel (een woordenboek/map in de meeste talen). Dat derde doel is het sleuteldoel — elk geldig TOML-bestand heeft precies één correct parseresultaat. Geen verrassende typecoercitie, geen YAML-stijl boolean-valkuilen, geen ambiguïteit over of een waarde een string of een getal is.

Het formaat leent de sectie-headerstijl van oude INI-bestanden maar voegt juiste typen, arrays en geneste tabellen toe. Het resultaat is iets dat vertrouwd aanvoelt voor iedereen die eerder een configuratiebestand heeft bewerkt, maar met genoeg structuur dat een parser een echt getypeerd datamodel kan geven. Versie 1.0.0 van de specificatie werd uitgebracht in januari 2021 na jaren van verfijning — je kunt de volledige TOML-specificatie op GitHub doorlezen als je in de randgevallen wilt duiken.

De Basis: Sleutel-Waardeparen en Opmerkingen

Een TOML-bestand bestaat uit sleutel-waardeparen. Sleutels en waarden worden gescheiden door =, en opmerkingen beginnen met #. Eenvoudig.

toml
# Cargo.toml — Rust package manifest
[package]
name = "image-resizer"
version = "0.4.2"
edition = "2021"
authors = ["Ada Lovelace <[email protected]>"]
description = "Fast image resizing with Lanczos3 resampling"
license = "MIT"
repository = "https://github.com/example/image-resizer"

# Integers, floats, booleans — all native types
max_threads = 8
quality_default = 0.85
verbose_logging = false

Geen aanhalingstekens vereist op de meeste sleutels. Waarden zijn getypeerd — 8 is een integer, 0.85 is een float, false is een boolean. Geen giswerk, geen impliciete coercitie op basis van hoe de waarde eruitziet. Dit is de dagelijkse TOML die je 90% van de tijd schrijft.

Stringtypen: Basic, Literal en Meerdere Regels

TOML heeft vier stringtypen. Dit dekt elk praktijkgeval netjes:

toml
# Basic strings — double quotes, support escape sequences
greeting = "Hello, \nworld!"
path = "C:\\Users\\ada\\Documents"

# Literal strings — single quotes, no escape processing at all
regex_pattern = '\d{4}-\d{2}-\d{2}'
windows_path = 'C:\Users\ada'

# Multiline basic string — triple double quotes
sql_query = """
  SELECT user_id, email, created_at
  FROM users
  WHERE active = true
    AND created_at > '2024-01-01'
  ORDER BY created_at DESC
"""

# Multiline literal string — triple single quotes, no escapes
shell_script = '''
#!/bin/bash
echo "Deploying $APP_NAME to $ENV"
kubectl apply -f k8s/
'''

Het letterlijke stringtype ('enkele aanhalingstekens') is het type dat mensen vergeten dat het bestaat, en het is genuien handig — regex-patronen en Windows-paden zijn veel schoner zonder escape- verdubbeling. Kies de aanhalingstekenstijl die je de minste backslashes laat schrijven.

Getallen, Booleans en Datetimes

De native typen van TOML dekken alles wat je werkelijk in een configuratiebestand zou zetten. Notabeen heeft het eersteklas datetime-ondersteuning — iets dat YAML technisch heeft maar inconsistent verwerkt over parsers heen.

toml
# Integers — underscores allowed as separators (like numeric literals in code)
max_connections = 1_000_000
port = 5432
hex_color = 0xFF6B6B      # hex prefix supported
octal_permissions = 0o755  # octal prefix supported

# Floats
pi = 3.14159265
compression_ratio = 1.5e-3
infinity_val = inf         # special values: inf, -inf, nan

# Booleans — lowercase only (not True, TRUE, yes, on)
ssl_enabled = true
dry_run = false

# Datetimes — RFC 3339 format
created_at = 2024-03-15T09:30:00Z
updated_at = 2024-03-15T14:22:10+05:30
log_date = 2024-03-15           # local date (no time)
backup_time = 03:00:00          # local time (no date)
TOML-booleans zijn strikt kleine letters. Alleen true en false — niet True, TRUE, yes, on, of een van de andere varianten die YAML 1.1 accepteert. Dit is opzettelijk. Als je een string "true" nodig hebt, zet er aanhalingstekens omheen.

Tabellen: De INI-Stijl Sectie-Headers

Tabellen in TOML worden gedefinieerd met de [header]-syntaxis. Alles onder een header behoort tot die tabel totdat de volgende header verschijnt. Dit is de functie die TOML vertrouwd laat aanvoelen — het zijn in wezen INI-bestanden maar met typen.

toml
[database]
host = "db.internal"
port = 5432
name = "app_production"
pool_size = 20

[database.credentials]
username = "app_user"
# Don't put real passwords here — use env vars or a secrets manager
password_env = "DB_PASSWORD"

[server]
host = "0.0.0.0"
port = 8080
workers = 4

[server.tls]
enabled = true
cert_file = "/etc/ssl/certs/app.crt"
key_file = "/etc/ssl/private/app.key"

Puntnotatie-headers zoals [database.credentials] maken geneste tabellen. Het geparsede resultaat is precies wat je zou verwachten: een database-object met een genest credentials-object. Je kunt ook inline tabellen schrijven voor eenvoudige gevallen — meer daarover hieronder.

Arrays en Array van Tabellen

Arrays in TOML gebruiken vierkante haakjes en kunnen meerdere regels beslaan. De echt onderscheidende TOML-functie is Array van Tabellen — gedefinieerd met dubbele haakjes [[header]]. Dit is TOML's antwoord op "hoe druk ik een lijst van objecten uit?" zonder dat het op JSON lijkt.

toml
# Regular arrays — can be split across lines, trailing comma is fine
allowed_origins = [
  "https://app.example.com",
  "https://admin.example.com",
  "http://localhost:3000",
]

supported_formats = ["jpeg", "png", "webp", "avif"]
retry_delays_ms = [100, 250, 500, 1000, 2000]

# Array of Tables — [[double brackets]]
# Each [[servers]] header appends a new object to the servers array
[[servers]]
name = "web-01"
ip = "10.0.1.10"
role = "primary"
tags = ["web", "prod"]

[[servers]]
name = "web-02"
ip = "10.0.1.11"
role = "replica"
tags = ["web", "prod"]

[[servers]]
name = "db-01"
ip = "10.0.2.10"
role = "primary"
tags = ["database", "prod"]

Die [[servers]]-syntaxis parseert naar een array van drie objecten — equivalent aan "servers": [{...}, {...}, {...}] in JSON. Het is uitgebreid vergeleken met JSON-arrays van objecten, maar het voordeel is leesbaarheid wanneer elk item veel velden heeft. Je kunt dit patroon uitgebreid zien in Cargo.toml-manifesten voor het definiëren van meerdere binaire doelen, voorbeelden en bench-vermeldingen.

Inline Tabellen: Compacte Eenregelers

Wanneer een tabel slechts een paar velden heeft en je er geen hele sectie-header voor wilt, laten inline tabellen je het op één regel schrijven:

toml
[build]
# Inline table — must stay on one line
target = { arch = "x86_64", os = "linux", libc = "musl" }

# Equivalent to writing:
# [build.target]
# arch = "x86_64"
# os = "linux"
# libc = "musl"

[feature_flags]
auth   = { enabled = true, rollout_pct = 100 }
search = { enabled = true, rollout_pct = 50 }
beta   = { enabled = false, rollout_pct = 0 }

Inline tabellen moeten op één regel blijven en kunnen later niet worden uitgebreid met een [header]. Gebruik ze voor kleine, samenhangende waardegroeperingen — ze zijn geweldig voor zaken zoals coördinatenparen, build-doelen of eenvoudige vlagconfiguraties. Gebruik ze niet als je meer dan drie of vier velden hebt; op dat punt is een gewone tabel beter leesbaar.

TOML in de Praktijk

TOML heeft een sterke niche veroverd in de Rust- en Python-ecosystemen en wint terrein elders. Dit is waar je het dagelijks tegenkomt:

  • Rust — Cargo.toml: elk Rust-project heeft er één. Definieert pakketmetadata, afhankelijkheden, functies en build-doelen. De Cargo-manifestreferentie is de meest gedetailleerde praktijkgids voor TOML-gebruik die je zult vinden.
  • Python — pyproject.toml: PEP 518 en PEP 621 hebben TOML gestandaardiseerd als het Python-projectmetadataformaat. Poetry, Hatch, PDM en setuptools lezen er allemaal uit.
  • Deno: het Deno-configuratiebestand ondersteunt TOML naast JSON.
  • Hugo: de Hugo static site generator accepteert TOML als configuratie- en front-matter-formaat — je ziet het tussen +++-scheidingstekens bovenaan Markdown-bestanden.
  • uv: de snelle Python-pakketbeheerder van Astral gebruikt pyproject.toml voor alle configuratie, waardoor TOML de de-facto standaard wordt voor nieuwe Python-tooling.

Als je een bestaande configuratie tussen formaten moet converteren, verwerken de TOML naar JSON- en JSON naar TOML-converters de structurele mapping voor je. Of gebruik de TOML Formatter om inconsistente spatiëring in een bestaand bestand op te ruimen, en de TOML Validator om syntaxfouten op te sporen voordat ze in productie opduiken.

TOML Parsen in Python

Sinds Python 3.11 bevat de standaardbibliotheek tomllib — geen externe afhankelijkheid nodig. Voor Python 3.9 en 3.10 heeft de tomli-backport een identieke API, zodat je tussen beide kunt schakelen met één importalias.

python
import sys

if sys.version_info >= (3, 11):
    import tomllib
else:
    import tomli as tomllib  # pip install tomli

# tomllib only reads binary mode — open with "rb"
with open("pyproject.toml", "rb") as f:
    config = tomllib.load(f)

# Types match TOML exactly: str, int, float, bool, datetime, list, dict
project_name = config["project"]["name"]               # str
python_requires = config["project"]["requires-python"] # str
dependencies = config["project"]["dependencies"]        # list[str]

print(f"Project: {project_name}")
print(f"Requires Python: {python_requires}")
print(f"Dependencies ({len(dependencies)}):")
for dep in dependencies:
    print(f"  {dep}")

# Parse from a string with tomllib.loads()
raw = """
[server]
host = "localhost"
port = 8080
debug = true
"""
server_config = tomllib.loads(raw)
print(server_config["server"]["port"])   # 8080 (int, not "8080")

Merk op dat tomllib.load() de binaire modus ("rb") vereist. Dit is opzettelijk — TOML vereist UTF-8-codering, en openen in binaire modus laat de parser de coderingscontrole zelf afhandelen. Het is een kleine valkuil die mensen de eerste keer betrapt.

TOML Parsen in Rust

In Rust is de toml-crate de standaardkeuze. Het integreert nauw met serde, zodat je direct naar je eigen structs kunt deserialiseren met minimale boilerplate:

rust
use serde::Deserialize;
use std::fs;

#[derive(Debug, Deserialize)]
struct AppConfig {
    server: ServerConfig,
    database: DatabaseConfig,
    feature_flags: FeatureFlags,
}

#[derive(Debug, Deserialize)]
struct ServerConfig {
    host: String,
    port: u16,
    workers: usize,
}

#[derive(Debug, Deserialize)]
struct DatabaseConfig {
    host: String,
    port: u16,
    name: String,
    pool_size: u32,
}

#[derive(Debug, Deserialize)]
struct FeatureFlags {
    enable_beta: bool,
    max_upload_mb: u32,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let raw = fs::read_to_string("config.toml")?;
    let config: AppConfig = toml::from_str(&raw)?;

    println!("Server: {}:{}", config.server.host, config.server.port);
    println!("DB pool size: {}", config.database.pool_size);
    println!("Beta enabled: {}", config.feature_flags.enable_beta);

    Ok(())
}

Voeg toml = "0.8" en serde = { version = "1", features = ["derive"] } toe aan je [dependencies] in Cargo.toml en je bent klaar. De serde derive-macro's verwerken alle veldmapping. Als je structveldnamen snake_case gebruiken maar de TOML-sleutels kebab-case gebruiken, voeg #[serde(rename_all = "kebab-case")] toe op structniveau en alles mapt automatisch.

TOML vs YAML vs JSON — Wanneer Welke Te Kiezen

Deze vraag komt op elk nieuw project naar boven. Hier is de eerlijke uiteenzetting:

  • TOML: het beste voor configuratiebestanden die mensen schrijven en onderhouden, waar getypeerde waarden van belang zijn en nesting oppervlakkig tot matig is. Sterk punt: app-configuraties, build-manifesten, toolinstellingen. Valt uiteen bij diepe nesting — 4+ niveaus worden onhandig met herhaalde sectie-headers.
  • YAML: het beste wanneer je gestructureerde gegevens schrijft met veel lijsten van objecten (Kubernetes-manifesten, GitHub Actions-workflows). Meerdere regels stringondersteuning is genuien beter dan TOML's. Het nadeel: witruimtegevoeligheid en YAML 1.1-typecoercitie creëren echte bugs in de praktijk.
  • JSON: het beste voor machine-naar-machine gegevensuitwisseling, API's en wanneer je de breedste mogelijke toolchain-ondersteuning nodig hebt. Niet geweldig voor door mensen onderhouden configuratie — geen opmerkingen en string-escaping is omslachtig.
Snelle vuistregel: als een ontwikkelaar het bestand handmatig in een teksteditor bewerkt, is TOML gewoonlijk de prettigste ervaring. Als het door een tool wordt gegenereerd of door een dozijn verschillende systemen in verschillende talen wordt verbruikt, wint JSON's universaliteit. YAML leeft in het midden — geweldig voor DevOps-configuratie die mensen schrijven maar tooling zwaar verwerkt.

Een Echte pyproject.toml

Om alles samen te brengen, hier is een realistisch pyproject.toml voor een Python-bibliotheek — het type dat je op een modern open-source project zou vinden. Merk op hoe het formaat veel gestructureerde informatie bevat terwijl het nog steeds gemakkelijk te scannen is:

toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "httpx-cache"
version = "1.2.0"
description = "Transparent HTTP caching layer for httpx"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.9"
authors = [
  { name = "Ada Lovelace", email = "[email protected]" },
]
keywords = ["http", "cache", "httpx", "async"]
classifiers = [
  "Development Status :: 4 - Beta",
  "Intended Audience :: Developers",
  "License :: OSI Approved :: MIT License",
  "Programming Language :: Python :: 3.9",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
]
dependencies = [
  "httpx>=0.25.0",
  "anyio>=4.0.0",
]

[project.optional-dependencies]
redis = ["redis>=5.0.0"]
dev = [
  "pytest>=7.4.0",
  "pytest-asyncio>=0.23.0",
  "coverage[toml]>=7.3.0",
  "ruff>=0.1.0",
  "mypy>=1.7.0",
]

[project.urls]
Homepage = "https://github.com/example/httpx-cache"
Changelog = "https://github.com/example/httpx-cache/blob/main/CHANGELOG.md"

[tool.ruff]
line-length = 100
target-version = "py39"

[tool.ruff.lint]
select = ["E", "F", "I", "UP"]

[tool.mypy]
strict = true
python_version = "3.9"

[tool.coverage.run]
source = ["httpx_cache"]
branch = true

[tool.coverage.report]
fail_under = 90

Dit is echte TOML die echt werk doet. Elke [tool.x]-sectie is een aparte naamruimte voor een ander hulpmiddel — ruff, mypy, coverage — die allemaal in één bestand leven zonder elkaar voor de voeten te lopen. Er is geen diepe nesting nodig, alles is op het eerste gezicht leesbaar.

Samenvatting

TOML houdt zijn belofte: leesbaar, ondubbelzinnig en netjes getypeerd. Als je een nieuw project start in Rust, Python of Go, is TOML het waard om standaard te worden voor configuratiebestanden — vooral bestanden die in bronbeheer worden vastgelegd en door meerdere mensen worden bewerkt. Het ontbreken van witruimtegevoeligheid alleen al maakt het een verlichting vergeleken met YAML. Voor het rechtstreeks werken met TOML-bestanden in je browser verwerken de TOML Formatter en TOML Validator de meest voorkomende taken. En als je een bestaand project migreert van JSON of TOML in een JSON-gebaseerde pijplijn wilt opnemen, hebben de TOML naar JSON- en JSON naar TOML-converters je gedekt.