Ta upp ett Rust-projekt och du hittar en Cargo.toml. Klona ett GitHub-repo och du hittar en .github/workflows/-mapp full av YAML. Båda formaten gör samma jobb på ytan — lagrar strukturerad konfiguration som människor redigerar — men de gör väldigt olika kompromisser. Om du någonsin haft YAML att tyst förvränga ett värde för dig, eller undrat varför Rust valde TOML istället för YAML för sitt paketmanifest, är den här artikeln för dig.

Kärnsskillnaden: explicit vs implicit

Den grundläggande uppdelningen mellan TOML och YAML handlar om hur mycket parsern tillåts gissa. TOML är explicit: varje värde har en otvetydig typ. Strängar citeras alltid. Booleaner är exakt true eller false. Datum/tider är förstklassiga värden med sin egen syntax. Det finns ingen implicit typkoercering — parsern försöker inte vara smart.

YAML lutar åt andra hållet. Det försöker vara bekvämt: du behöver inga citationstecken runt de flesta strängar, och parsern härledar typer från värdets utseende. Det är det härledandet som biter folk. YAML 1.1-specifikationen (fortfarande använd av många verktyg) behandlar yes, no, on, off, true och false alla som booleaner. Den behandlar okoterade versionssträngar som 1.0 som flyttal. Och sedan är det Norge-problemet.

YAMLs berömda fallgropar

Norge-problemet blev något av ett meme i DevOps-kretsar. I YAML 1.1 parsas landskoden NO som booleanen false. Så en konfiguration som mappar ISO-landskoder till inställningar skulle tyst konvertera Norges post till ett booleskt värde. YAML 1.2-specifikationen fixade detta, men många flitigt använda parsers — inklusive PyYAMLs standardläge tills nyligen — riktar sig fortfarande mot YAML 1.1. Kontrollera vilken spec-version dina verktyg faktiskt implementerar innan du litar på dem.

Norge-problemet i praktiken: I YAML 1.1 blir okoterade NO, Yes, on och off alla booleaner. Lösningen är enkel — citera dina strängar — men problemet är att det är tyst. Din konfiguration laddas utan fel och du får ett booleskt värde där du förväntade dig en sträng.
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"
  • Oktala literaler. I YAML 1.1 parsas 010 som 8 (oktalt), inte 10. Det spelar roll för filbehörighetsvärden som 0755.
  • Tab vs mellanslag. YAML förbjuder tabbtecken för indragning. Klistra in kod från en editor konfigurerad för att använda tabbar och du får ett kryptiskt parse-fel — eller värre, tyst feljustering.
  • Implicita null.. En nyckel utan värde blir null. Lätt att skapa av misstag vid manuell redigering.
  • Indragningskänslighet. Ett extra mellanslag och ett värde skiftar tyst från en förälder till en annan. Inget fel, bara fel data.

TOML: hur explicita typer faktiskt ser ut

TOMLs typsystem beskrivs i TOML v1.0-specifikationen. Strängar måste citeras (enkla eller dubbla). Booleaner är true eller false och inget annat. Heltal är heltal. Flyttal är flyttal. Och datum/tider — något som varken JSON eller YAML har som en inbyggd typ — är förstklassiga värden 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

Landskoden NO är bara en sträng i TOML eftersom den inte är citerad, men TOML härleder inte typer från strängliknande värden — okoterade värden följer strikta syntaktiska regler. För att vara en sträng behöver den citationstecken. TOML har helt enkelt inte den implicita koerceringsmekanismen som orsakar YAMLs fallgropar.

Sida vid sida: en CI-pipeline-konfiguration

Här är ett GitHub Actions-arbetsflöde — det här är YAMLs hemmaplan. Formatet passar naturligt eftersom GitHub Actions förväntar sig YAML, indragningsbaserad nesting speglar den logiska strukturen och kommentarer är väsentliga för att förklara icke-uppenbara stegkonfigurationer.

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

Här är nu det ekvivalenta projektmanifestet i TOML — det är där TOML lyser. Jämför en riktig Cargo.toml-struktur med hur samma konfiguration skulle se ut 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 svagheter: Array of Tables

TOML är inte utan sina ojämna kanter. Syntaxen för arraytabeller — [[dubbla parenteser]] — är ett av de mer förvirrande sakerna i specifikationen. Det är hur du uttrycker vad JSON skulle skriva som en array av objekt, och det läses konstigt till en början.

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äser samma struktur mer naturligt — bara en lista med indragna egenskaper. För djupt kapslad data med upprepade arrayposter är YAMLs indragningsbaserade nesting genuint mindre utförlig än TOMLs [[sektion]]-rubriker. TOML har heller ingen ekvivalent till YAMLs ankare-och-alias-system, så du kan inte definiera ett delat block en gång och referera till det på annat håll. Om du befinner dig med att duplicera samma uppsättning värden i flera TOML-tabeller är du fast med att kopiera dem manuellt.

Var varje format vinner i praktiken

YAML vinner som standard i dessa ekosystem:

  • GitHub Actions. Hela arbetsflödessyntaxen är YAML. Du har inget val här, och det är okej — indragningen mappar väl till den kapslade strukturen av jobb, steg och villkor.
  • Kubernetes. Varje manifest — Deployments, Services, ConfigMaps, Ingress-regler — är YAML. Kubernetes objektmodell är djupt kapslad och YAML hanterar det elegant.
  • Docker Compose. Tjänstedefinitioner, nätverk, volymer — allt YAML. Kommentarer som förklarar varför en port är exponerad eller varför ett specifikt healthcheck-intervall används är en del av dokumentationen.
  • Ansible. Playbooks, roller, variabelfiler — YAML genomgående. Kommentarsstödet används aktivt för att förklara icke-uppenbara uppgiftsparametrar.

TOML vinner när du kontrollerar formatet:

  • Rust-projekt. Cargo.toml är guldstandarden. Beroendedeklarationer, funktionsflaggor, byggprofiler — allt i TOML. De explicita typerna innebär att versionssträngar som "1.0.0" förblir strängar.
  • Python-projekt. pyproject.toml har blivit standarden för Python-projektmetadata, byggkonfiguration och verktygsinställningar (Black, isort, mypy, pytest läser alla från den).
  • Verktygskonfiguration där tvetydighet orsakar buggar. Om din konfiguration innehåller versionssträngar, landskoder eller andra värden som ser ut att kunna vara booleaner eller tal eliminerar TOMLs explicita typer en hel kategori av parsöverraskningar.
  • Platta konfigurationer. När din konfiguration till stor del är nyckel-värde-par på en eller två nivåer av nesting är TOML mer läsbar än YAML och renare än JSON.

Den praktiska beslutsguiden

Det mesta av tiden görs valet åt dig av ekosystemet. GitHub Actions är YAML. Kubernetes är YAML. Rust är TOML. Python-verktyg är TOML. När du faktiskt har ett fritt val, använd detta som din guide:

  • Använd YAML när verktyget kräver det — CI/CD-plattformar, Kubernetes, Helm charts, Docker Compose, Ansible. Att kämpa mot konventionen kostar mer än det sparar.
  • Använd YAML när du behöver ankare och alias för att hålla en komplex konfiguration DRY — det finns ingen TOML-ekvivalent.
  • Använd TOML för projektmanifest och verktygskonfiguration du kontrollerar — paketmetadata, linter-inställningar, byggkonfiguration.
  • Använd TOML när din konfiguration innehåller värden som YAML 1.1 kan feltolka — versionssträngar, landskoder, vad som helst som liknar ett booleskt värde eller tal.
  • Citera dina YAML-strängar när värdet kan förväxlas med ett booleskt värde, tal eller null. Speciellt: versionsnummer, landskoder, vad som helst som börjar med en siffra och värden som yes, no, on, off.

Arbeta med båda formaten

Behöver du validera eller formatera en konfigurationsfil du arbetar med? TOML Formatter hanterar TOML-filer och YAML Formatter täcker YAML. Om du behöver migrera en konfiguration från ett format till ett annat — säg, konvertera en YAML-baserad verktygskonfiguration till TOML för ett projekt som föredrar det — konverterar båda TOML till JSON och YAML till JSON till JSON, som du sedan kan konvertera till ditt målformat. Ibland är att gå igenom JSON som ett mellanliggande steg den mest pålitliga vägen.

Värt att veta: båda formaten har sina egna JSON-kompatibla delmängder. Giltig JSON är giltig YAML (YAML är en supermängd av JSON). TOML har inte den relationen med JSON, men TOML-specifikationen hålls avsiktligt enkel — det är en kort läsning, vilket är mer än du kan säga om hela YAML 1.2-specifikationen.

Sammanfattning

TOML och YAML konkurrerar inte riktigt. De har slagit sig ned i olika nischer baserat på sina designprioriteringar. YAMLs implicita typer och indragningsbaserade struktur gör det till den naturliga passformen för stora, kapslade konfigurationer underhållna av team — tänk Kubernetes-manifest och GitHub Actions-arbetsflöden. TOMLs explicita typer och platta sektionsstruktur gör det till den naturliga passformen för projektmanifest och verktygskonfiguration där en feltolkad versionssträng eller landskod skulle orsaka en riktig bugg.

Det enda att ta med sig: om du skriver YAML och har några värden som ser ut att kunna vara booleaner, tal eller null — citera dem. Det är den enda vanan som förhindrar de flesta YAML-relaterade konfigurationsbuggar. Och om du startar ett nytt projekt och fritt kan välja ditt konfigurationsformat är TOML värt en titt. TOML GitHub-repositoriet har bra exempel, och när du väl har skrivit en Cargo.toml eller pyproject.toml blir formatets lockelse ganska tydlig.