Tag et hvilket som helst Rust-projekt og du finder en Cargo.toml. Klon et GitHub-repo og du finder en .github/workflows/-mappe fuld af YAML. Begge formater udfører det samme overordnede job — at gemme struktureret konfiguration som mennesker redigerer — men de træffer meget forskellige afvejninger. Hvis du nogensinde har oplevet at YAML lydløst forvansker en værdi, eller undret dig over hvorfor Rust valgte TOML frem for YAML til sin pakkemanifest, er denne artikel til dig.

Den Centrale Forskel: Eksplicit vs Implicit

Den grundlæggende skillelinje mellem TOML og YAML handler om, hvor meget parseren må gætte. TOML er eksplicit: hver værdi har en utvetydig type. Strenge er altid i anførselstegn. Booleske værdier er præcis true eller false. Datetider er førsteklasses værdier med deres egen syntaks. Der er ingen implicit typekonvertering — parseren forsøger ikke at være klog.

YAML hælder den anden vej. Det forsøger at være bekvemt: du behøver ikke anførselstegn rundt om de fleste strenge, og parseren udleder typer fra værdiens udseende. Det er den slutning, der bider folk. YAML 1.1-specifikationen (stadig brugt af mange værktøjer) behandler yes, no, on, off, true og false alle som booleske værdier. Den behandler ukoterede versionsstrenge som 1.0 som float. Og så er der Norge-problemet.

YAMLs Berømte Fælder

Norge-problemet blev noget af et meme i DevOps-kredse. I YAML 1.1 parses landekoden NO som boolesk false. Så en konfiguration, der mapper ISO-landekoder til indstillinger, ville lydløst konvertere Norges post til et boolesk udtryk. Den YAML 1.2-specifikationen rettede dette, men mange meget brugte parsere — herunder PyYAML's standardtilstand indtil for nylig — retter sig stadig mod YAML 1.1. Kontrollér hvilken spec-version dit værktøj faktisk implementerer, før du stoler på det.

Norge-problemet i praksis: I YAML 1.1 bliver ukoteret NO, Yes, on og off alle til booleske værdier. Løsningen er enkel — sæt anførselstegn om dine strenge — men problemet er, at det sker lydløst. Din konfiguration indlæses uden fejl og du får et boolesk udtryk, hvor du forventede en streng.
yaml
# YAML 1.1 implicit type coercion — all of these silently become booleans:
countries:
  norway: NO        # → false  ← The Norway Problem
  sweden: SE        # → "SE"   (fine, not in the boolean list)
  enabled: yes      # → true
  disabled: no      # → false
  feature_flag: on  # → true
  another: off      # → false

# Version strings can become numbers:
python_version: 3.10   # → float 3.1 (trailing zero dropped)
api_version: 1.0       # → float 1.0

# Safe: quote anything that could be ambiguous
python_version: "3.10"
country: "NO"
enabled: "yes"
  • Oktale literals. I YAML 1.1 parses 010 som 8 (oktal), ikke 10. Det har betydning for filrettighedsværdier som 0755.
  • Tab vs. mellemrum. YAML forbyder tabulatortegn til indrykning. Indsæt kode fra en editor konfigureret til at bruge tabs og du får en kryptisk parse-fejl — eller endnu værre, stille fejlplacering.
  • Implicitte null-værdier. En nøgle uden værdi bliver null. Let at oprette ved et uheld, når man redigerer manuelt.
  • Indrykningsfølsomhed. Et ekstra mellemrum og en værdi flyttes lydløst fra en forælder til en anden. Ingen fejl, bare forkerte data.

TOML: Hvordan Eksplicitte Typer Faktisk Ser Ud

TOMLs typesystem er beskrevet i TOML v1.0-specifikationen. Strenge skal være i anførselstegn (enkelt eller dobbelt). Booleske værdier er true eller false og intet andet. Heltal er heltal. Float er float. Og datetider — noget hverken JSON eller YAML har som native type — er førsteklasses værdier i TOML.

toml
# TOML types are always unambiguous
name = "my-app"          # string — must be quoted
version = "1.0.0"        # string — quotes make it clear this is not a float
port = 8080              # integer
debug = false            # boolean — only true/false, nothing else
threshold = 0.95         # float

# Datetime is a first-class type — no string parsing needed
created_at = 2024-01-15T09:30:00Z
build_date = 2024-03-20

# Arrays
allowed_hosts = ["localhost", "staging.example.com", "api.example.com"]

# Inline tables
[database]
host = "postgres.internal"
port = 5432
name = "payments_prod"
pool_size = 20

Landekoden NO er blot en streng i TOML, fordi den ikke er i anførselstegn, men TOML udleder ikke typer fra strenglignende værdier — ukoterede værdier følger strenge syntaktiske regler. For at være en streng kræves anførselstegn. TOML har simpelthen ikke den implicitte konverteringsmekanisme, der forårsager YAMLs fælder.

Side om Side: En CI Pipeline-konfiguration

Her er et GitHub Actions-workflow — dette er YAMLs hjemmebane. Formatet passer naturligt fordi GitHub Actions forventer YAML, den indrykning-baserede nesting spejler den logiske struktur, og kommentarer er essentielle til at forklare ikke-åbenlyse trinkonfigurationer.

yaml
# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build-and-publish:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build --if-present

      - name: Publish to npm
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Her er den tilsvarende projektmanifest i TOML — dette er hvor TOML skinner. Sammenlign en rigtig Cargo.toml-struktur med hvordan den samme konfiguration ville se ud i YAML:

toml
# Cargo.toml — Rust package manifest
[package]
name = "payments-service"
version = "2.4.1"
edition = "2021"
description = "Payment processing microservice"
license = "MIT"
authors = ["Alice Chen <[email protected]>"]

[dependencies]
tokio = { version = "1.35", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-native-tls"] }
tracing = "0.1"
anyhow = "1.0"

[dev-dependencies]
tokio-test = "0.4"
wiremock = "0.6"

[profile.release]
opt-level = 3
lto = true
codegen-units = 1

TOMLs Svagheder: Array af Tabeller

TOML er ikke uden ru kanter. Syntaksen for arrays af tabeller — [[dobbelte parenteser]] — er en af de mere forvirrende ting i specifikationen. Det er måden, du udtrykker hvad JSON ville skrive som et array af objekter, og det læses mærkeligt i starten.

toml
# TOML array of tables — [[double brackets]] creates array entries
[[server]]
host = "web-01.example.com"
port = 443
region = "us-east-1"

[[server]]
host = "web-02.example.com"
port = 443
region = "us-west-2"

[[server]]
host = "web-03.example.com"
port = 443
region = "eu-west-1"

# The above is equivalent to this JSON:
# { "server": [
#   { "host": "web-01.example.com", "port": 443, "region": "us-east-1" },
#   { "host": "web-02.example.com", "port": 443, "region": "us-west-2" },
#   { "host": "web-03.example.com", "port": 443, "region": "eu-west-1" }
# ]}

I YAML læses den samme struktur mere naturligt — bare en liste med indrykkede egenskaber. For dybt nestede data med gentagne array-poster er YAMLs indrykning-baserede nesting faktisk mindre verbose end TOMLs [[sektion]]-overskrifter. TOML har heller ikke et ækvivalent til YAMLs anker-og-alias-system, så du kan ikke definere en delt blok én gang og referere til den andre steder. Hvis du finder dig selv i at duplikere det samme sæt værdier i flere TOML-tabeller, er du nødt til at kopiere dem manuelt.

Hvor Hvert Format Vinder i Praksis

YAML vinder som standard i disse økosystemer:

  • GitHub Actions. Hele workflow-syntaksen er YAML. Du har ikke noget valg her, og det er fint — indrykningen mapper godt til den nestede struktur af jobs, trin og betingelser.
  • Kubernetes. Hver manifest — Deployments, Services, ConfigMaps, Ingress-regler — er YAML. Den Kubernetes-objektmodellen er dybt nested, og YAML håndterer det elegant.
  • Docker Compose. Servicedefinitioner, netværk, volumes — alt YAML. Kommentarer der forklarer hvorfor en port er eksponeret eller hvorfor et specifikt healthcheck-interval bruges, er en del af dokumentationen.
  • Ansible. Playbooks, roller, variabelfiler — YAML overalt. Kommentarsupport bruges aktivt til at forklare ikke-åbenlyse opgaveparametre.

TOML vinder, når du kontrollerer formatet:

  • Rust-projekter. Cargo.toml er guldstandarden. Afhængighedsdeklarationer, feature flags, build-profiler — alt i TOML. De eksplicitte typer betyder at versionsstrenge som "1.0.0" forbliver strenge.
  • Python-projekter. pyproject.toml er blevet standarden for Python- projektmetadata, build-konfiguration og værktøjsindstillinger (Black, isort, mypy, pytest læser alle fra den).
  • Værktøjskonfiguration, hvor tvetydighed forårsager fejl. Hvis din konfiguration indeholder versions- strenge, landekoder eller andre værdier, der ligner booleske udtryk eller tal, eliminerer TOMLs eksplicitte typer en hel kategori af parsing-overraskelser.
  • Flade konfigurationer. Når din konfiguration primært er nøgle-værdi-par på et eller to niveauer af nesting, er TOML mere læsbar end YAML og renere end JSON.

Den Praktiske Beslutningsguide

Det meste af tiden er valget truffet for dig af økosystemet. GitHub Actions er YAML. Kubernetes er YAML. Rust er TOML. Python-værktøjer er TOML. Når du faktisk har et frit valg, brug dette som din guide:

  • Brug YAML, når værktøjet kræver det — CI/CD-platforme, Kubernetes, Helm-charts, Docker Compose, Ansible. At kæmpe mod konventionen koster mere end det sparer.
  • Brug YAML, når du har brug for ankre og aliaser til at holde en kompleks konfiguration DRY — der er ingen TOML-ækvivalent.
  • Brug TOML til projektmanifester og værktøjskonfiguration du kontrollerer — pakke-metadata, linter-indstillinger, build-konfiguration.
  • Brug TOML, når din konfiguration indeholder værdier som YAML 1.1 måske fortolker forkert — versionsstrenge, landekoder, alt der ligner et boolesk udtryk eller tal.
  • Sæt anførselstegn om dine YAML-strenge, når værdien kan forveksles med et boolesk udtryk, tal eller null. Især: versionsnumre, landekoder, alt der starter med et ciffer, og værdier som yes, no, on, off.

Arbejde med Begge Formater

Har du brug for at validere eller formatere en konfigurationsfil du arbejder på? TOML-formatering håndterer TOML-filer, og YAML-formatering dækker YAML. Hvis du har brug for at migrere en konfiguration fra et format til et andet — f.eks. konvertere en YAML-baseretværktøjskonfiguration til TOML for et projekt der foretrækker det — outputter TOML til JSON og YAML til JSON-konverterne begge JSON, som du derefter kan konvertere til dit målformat. Nogle gange er det at gå gennem JSON som et mellemliggende trin den mest pålidelige vej.

Noget værd at vide: begge formater har deres egne JSON-kompatible undersæt. Gyldig JSON er gyldig YAML (YAML er et supersæt af JSON). TOML har ikke det forhold med JSON, men TOML-specifikationen er bevidst holdt enkel — det er en kort læsning, hvilket er mere end man kan sige for den fulde YAML 1.2-specifikation.

Afrunding

TOML og YAML konkurrerer egentlig ikke. De har fundet sig til rette i forskellige nicheområder baseret på deres designprioriteter. YAMLs implicitte typer og indrykning-baserede struktur gør det til det naturlige valg for store, nestede konfigurationer vedligeholdt af teams — tænk Kubernetes-manifester og GitHub Actions- workflows. TOMLs eksplicitte typer og flade sektionsstruktur gør det til det naturlige valg for projekt- manifester og værktøjskonfiguration, hvor en forkert læst versionsstreng eller landekode ville forårsage en reel fejl.

Det ene du bør tage med dig: hvis du skriver YAML og har værdier der ligner booleske udtryk, tal eller null — sæt anførselstegn om dem. Det er den ene vane, der forhindrer de fleste YAML-relaterede konfigurationsfejl. Og hvis du starter et nyt projekt og kan vælge dit konfigurationsformat frit, er TOML værd at kigge på. Det TOML GitHub-repository har gode eksempler, og når du har skrevet en Cargo.toml eller pyproject.toml, bliver formatets appel ret tydelig.