Hvis du har åbnet et Rust-projekt, har du mødt TOML. Hvis du har rørt ved en Python-pakke der bruger pyproject.toml, har du også brugt det. TOML — Tom's Obvious Minimal Language — er konfigurationsfilformatet der stille og roligt vinder over udviklere der er trætte af YAMLs mellemrum-faldgruber og JSONs afvisning af at tillade kommentarer. Skabt af Tom Preston-Werner, medstifter af GitHub, blev TOML designet omkring én idé: et konfigurationsformat skal være så oplagt at du kan læse det uden en specifikation. Lad os se om det lever op til det løfte.

Hvad er TOML præcis?

TOML er et konfigurationsfilformat med tre eksplicitte designmål, angivet direkte øverst i den officielle specifikation: det skal være oplagt at læse, minimalt i kompleksitet og mappe entydigt til en hashtabel (et dictionary/map i de fleste sprog). Det tredje mål er det vigtigste — enhver gyldig TOML-fil har præcis ét korrekt parsingresultat. Ingen overraskende typetvang, ingen YAML-booleske faldgruber, ingen tvetydighed om hvorvidt en værdi er en streng eller et tal.

Formatet låner sektionsoverskriftsstilen fra gamle INI-filer men tilføjer ordentlige typer, arrays og indlejrede tabeller ovenpå. Resultatet er noget der føles bekendt for enhver der har redigeret en konfigurationsfil, men med tilstrækkelig struktur til at en parser kan give dig en reel typed datamodel ud af det. Version 1.0.0 af specifikationen blev udgivet i januar 2021 efter år med forfining — du kan gennemse hele TOML-specifikationen på GitHub hvis du vil grave i kanttilfælde.

Grundlaget: nøgle-værdi-par og kommentarer

En TOML-fil er opbygget af nøgle-værdi-par. Nøgler og værdier er adskilt af =, og kommentarer starter med #. Enkelt.

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

Ingen anførselstegn krævet på de fleste nøgler. Værdier er typede — 8 er et heltal, 0.85 er et flyttal, false er en boolean. Ingen gætning, ingen implicit tvang baseret på hvordan værdien ser ud. Det er den hverdagslige TOML du vil skrive 90% af tiden.

Strengtyper: grundlæggende, literale og flerlinjer

TOML har fire strengtyper. Det dækker alle virkelige tilfælde rent:

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/
'''

Den literale strengtype ('enkle anførselstegn') er den som folk glemmer eksisterer, og den er genuint nyttig — regex-mønstre og Windows-stier er meget renere uden escape-fordobling. Vælg den anførselstegns-stil der betyder at du skriver færre backslashes.

Tal, booleaner og datoer/tider

TOMLs native typer dækker alt du faktisk ville lægge i en konfigurationsfil. Bemærkelsesværdigt har det førsteklasses understøttelse af dato/tid — noget YAML teknisk set har men håndterer inkonsekvent på tværs af parsere.

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-booleaner er strengt lowercase. Kun true og false — ikke True, TRUE, yes, on eller nogen af de andre varianter YAML 1.1 accepterer. Det er bevidst. Hvis du har brug for strengen "true", citér den.

Tabeller: INI-stilede sektionsoverskrifter

Tabeller i TOML defineres med [overskrift]-syntaks. Alt under en overskrift tilhører den tabel indtil næste overskrift vises. Det er den funktion der gør TOML bekendt — det er i bund og grund INI-filer men med typer.

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"

Prikkede overskrifter som [database.credentials] opretter indlejrede tabeller. Det parsede resultat er præcis hvad du ville forvente: et database-objekt med et indlejret credentials-objekt. Du kan også skrive inline-tabeller til simple tilfælde — mere om det nedenfor.

Arrays og arrays af tabeller

Arrays i TOML bruger firkantede parenteser og kan strække sig over flere linjer. Den virkelig karakteristiske TOML-funktion er Array of Tables — defineret med dobbelte parenteser [[overskrift]]. Det er TOMLs svar på "hvordan udtrykker jeg en liste af objekter?" uden at det ser ud som JSON.

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"]

Den [[servers]]-syntaks parser til et array af tre objekter — svarende til "servers": [{...}, {...}, {...}] i JSON. Det er mere omstændeligt sammenlignet med JSON-arrays af objekter, men fordelen er læsbarhed når hvert element har mange felter. Du kan se dette mønster flittigt i Cargo.toml-manifester til definition af flere binære mål, eksempler og benchmark-poster.

Inline-tabeller: kompakte enkeltlinjer

Når en tabel kun har et par felter og du ikke ønsker en hel sektionsoverskrift til den, lader inline-tabeller dig skrive det på en enkelt linje:

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-tabeller skal forblive på en enkelt linje og kan ikke udvides senere med en [overskrift]. Brug dem til små, sammenhængende værdigrupper — de er fremragende til ting som koordinatpar, byggmål eller simple flagkonfigurationer. Brug dem ikke når du har mere end tre eller fire felter; på det tidspunkt læser en normal tabel bedre.

TOML i den virkelige verden

TOML har udskåret sig en stærk niche i Rust- og Python-økosystemerne og vinder indpas andetsteds. Her er hvor du støder på det dagligt:

  • Rust — Cargo.toml: Hvert Rust-projekt har en. Definerer pakke-metadata, afhængigheder, funktioner og byggmål. Cargo-manifest-referencen er den mest detaljerede virkelige TOML-brugsguide du finder.
  • Python — pyproject.toml: PEP 518 og PEP 621 standardiserede TOML som Python-projektmetadataformatet. Poetry, Hatch, PDM og setuptools læser alle fra det.
  • Deno: Denos konfigurationsfil understøtter TOML ud over JSON.
  • Hugo: Den statiske website-generator Hugo accepterer TOML som konfiguration og front-matter-format — du ser det mellem +++-afgrænsere øverst i Markdown-filer.
  • uv: Den hurtige Python-pakkeadministrator fra Astral bruger pyproject.toml til al konfiguration, hvilket gør TOML til de facto-standard for nye Python-værktøjer.

Hvis du skal konvertere en eksisterende konfiguration mellem formater, håndterer TOML til JSON og JSON til TOML-konverterne den strukturelle mapping for dig. Eller brug TOML Formatter til at rydde op i inkonsekvent afstand i en eksisterende fil og TOML Validator til at fange syntaksfejl inden de dukker op i produktion.

Parsing af TOML i Python

Siden Python 3.11 inkluderer standardbiblioteket tomllib — ingen ekstern afhængighed nødvendig. Til Python 3.9 og 3.10 har backporten tomli et identisk API, så du kan skifte mellem dem med et enkelt 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")

Bemærk at tomllib.load() kræver binær tilstand ("rb"). Det er bevidst — TOML kræver UTF-8-kodning, og at åbne i binær tilstand lader parseren selv håndtere kodningskontrol. Det er en lille faldgrube der fanger folk første gang.

Parsing af TOML i Rust

I Rust er craten toml standardvalget. Den integrerer tæt med serde, så du kan deserialisere direkte til dine egne structs med minimal standardkode:

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(())
}

Tilføj toml = "0.8" og serde = { version = "1", features = ["derive"] } til din [dependencies] i Cargo.toml og du er klar. Serde derive-makroerne håndterer al feltmapping. Hvis dine struct-feltnavne bruger snake_case men TOML-nøglerne bruger kebab-case, tilføj #[serde(rename_all = "kebab-case")] på struct-niveau og alt mapper automatisk.

TOML vs YAML vs JSON — hvornår man skal vælge hvad

Dette spørgsmål dukker op i hvert nyt projekt. Her er den ærlige opdeling:

  • TOML: Bedst til konfigurationsfiler som mennesker skriver og vedligeholder, hvor typede værdier er vigtige og indlejring er lav til moderat. Styrke: appkonfigurationer, byggmanifester, værktøjsindstillinger. Bryder ned ved dyb indlejring — 4+ niveauer bliver besværlige med gentagne sektionsoverskrifter.
  • YAML: Bedst når du skriver strukturerede data med masser af lister-af-objekter (Kubernetes-manifester, GitHub Actions-arbejdsflows). Understøttelse af flerlinjestrenge er genuint bedre end TOMLs. Ulempen: mellemrum-følsomhed og YAML 1.1-typetvang skaber reelle fejl i praksis.
  • JSON: Bedst til maskine-til-maskine-dataudveksling, API'er og når du har brug for den bredest mulige værktøjsunderstøttelse. Ikke godt til menneskelig konfiguration — ingen kommentarer, og strengescaping er besværlig.
Hurtig tommelfingerregel: Hvis en udvikler redigerer filen i hånden i en teksteditor, er TOML normalt den mest behagelige oplevelse. Hvis det er genereret af et værktøj eller forbrugt af et dusin forskellige systemer i forskellige sprog, vinder JSONs universalitet. YAML lever i midten — fantastisk til DevOps-konfiguration som mennesker skriver men værktøjer bearbejder kraftigt.

En rigtig pyproject.toml

For at sætte det hele sammen, her er en realistisk pyproject.toml for et Python-bibliotek — den slags du finder i et moderne open source-projekt. Bemærk hvordan formatet bærer en masse struktureret information mens det stadig er let at skanne:

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

Det er rigtig TOML der udfører rigtigt arbejde. Hvert [tool.x]-afsnit er et separat navnerum for et andet værktøj — ruff, mypy, coverage — alle lever i én fil uden at træde på hinandens tæer. Ingen dyb indlejring nødvendig, alt er læsbart med et blik.

Opsummering

TOML leverer på sit løfte: læsbart, entydigt og rent typet. Hvis du starter et nyt projekt i Rust, Python eller Go er TOML værd at vælge som standard til konfigurationsfiler — særligt filer der vil blive committed til kildekontrol og redigeret af flere personer. Manglen på mellemrum-følsomhed alene er en lettelse sammenlignet med YAML. Til at arbejde med TOML-filer direkte i din browser, håndterer TOML Formatter og TOML Validator de mest almindelige opgaver. Og hvis du migrerer et eksisterende projekt fra JSON eller skal bygge bro over TOML til en JSON-baseret pipeline, har konverterne TOML til JSON og JSON til TOML dig dækket.