Weź dowolny projekt Rust i znajdziesz Cargo.toml. Sklonuj repozytorium GitHub i
znajdziesz folder .github/workflows/ pełen YAML. Oba formaty wykonują tę samą pracę na poziomie
powierzchownym — przechowywanie strukturalnej konfiguracji, którą ludzie edytują — ale dokonują bardzo różnych
kompromisów. Jeśli kiedykolwiek YAML cicho zniekształcił wartość, lub zastanawiałeś się, dlaczego Rust wybrał
TOML zamiast YAML dla manifestu pakietu, ten artykuł jest dla Ciebie.
Podstawowa różnica: jawne vs niejawne
Fundamentalny podział między TOML i YAML dotyczy tego, ile może zgadywać parser.
TOML jest jawny: każda
wartość ma jednoznaczny typ. Ciągi są zawsze cytowane. Wartości logiczne to dokładnie true
lub false. Daty/godziny to wartości pierwszej klasy z własną składnią. Nie ma
niejawnej koercji typów — parser nie próbuje być sprytny.
YAML skłania się w drugą stronę.
Stara się być wygodny: nie potrzebujesz cudzysłowów wokół większości ciągów, a parser wnioskuje typy
z wyglądu wartości. To wnioskowanie jest tym, co uderza ludzi. Specyfikacja YAML 1.1
(nadal używana przez wiele narzędzi) traktuje yes, no, on, off,
true i false jako wartości logiczne. Traktuje niecytowane ciągi wersji jak
1.0 jako liczby zmiennoprzecinkowe. A potem jest Problem Norwegii.
Słynne pułapki YAML
Problem Norwegii stał się swoistym memem w kręgach DevOps. W YAML 1.1, kod kraju
NO parsuje się jako wartość logiczna false. Więc konfiguracja mapująca kody krajów
ISO na ustawienia cicho konwertowałaby wpis Norwegii na wartość logiczną.
Specyfikacja YAML 1.2 naprawiła to,
ale wiele szeroko używanych parserów — w tym domyślny tryb PyYAML do niedawna — nadal celuje w YAML 1.1.
Sprawdź, którą wersję specyfikacji faktycznie implementuje Twoje narzędzie, zanim mu zaufasz.
NO,
Yes, on i off wszystkie stają się wartościami logicznymi. Naprawą jest
prosto — cytuj swoje ciągi — ale problem polega na tym, że jest cichy. Twoja konfiguracja ładuje się bez błędu
i otrzymujesz wartość logiczną tam, gdzie spodziewałeś się ciągu.# 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"- Literały ósemkowe. W YAML 1.1,
010parsuje się jako8(ósemkowo), nie10. Ma to znaczenie dla wartości uprawnień plików jak0755. - Tab vs spacja. YAML zabrania znaków tabulacji do wcięcia. Wklej kod z edytora skonfigurowanego do używania tabulacji i uzyskasz kryptyczny błąd parsowania — lub gorzej, ciche przesunięcie.
- Niejawne nulle. Klucz bez wartości staje się
null. Łatwo stworzyć przez przypadek podczas ręcznej edycji. - Wrażliwość na wcięcia. Jedna dodatkowa spacja i wartość cicho przesuwa się z jednego rodzica do innego. Brak błędu, po prostu złe dane.
TOML: jak wyglądają jawne typy
System typów TOML jest opisany w
specyfikacji TOML v1.0. Ciągi muszą
być cytowane (pojedyncze lub podwójne). Wartości logiczne to true lub false i nic innego.
Liczby całkowite to liczby całkowite. Liczby zmiennoprzecinkowe to liczby zmiennoprzecinkowe. I daty/godziny —
czegoś, czego ani JSON, ani YAML nie ma jako natywnego typu — są wartościami pierwszej klasy w 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 = 20Kod kraju NO jest po prostu ciągiem w TOML, ponieważ nie jest cytowany, ale TOML
nie wnioskuje typów z wartości podobnych do ciągów — niecytowane wartości podlegają ścisłym regułom składniowym.
Aby być ciągiem, potrzebuje cudzysłowów. TOML po prostu nie ma mechanizmu niejawnej koercji, który powoduje
pułapki YAML.
Porównanie obok siebie: konfiguracja pipeline CI
Oto przepływ pracy GitHub Actions — to jest naturalne terytorium YAML. Format pasuje naturalnie, ponieważ GitHub Actions oczekuje YAML, wcięcie oparte na zagnieżdżeniu odzwierciedla logiczną strukturę, a komentarze są niezbędne do wyjaśnienia nieoczywistych konfiguracji kroków.
# .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 }}Teraz oto równoważny manifest projektu w TOML — tu właśnie TOML błyszczy. Porównaj prawdziwą strukturę Cargo.toml z tym, jak ta sama konfiguracja wyglądałaby w YAML:
# 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 = 1Słabości TOML: Array of Tables
TOML nie jest bez szorstkich krawędzi. Składnia dla tablic tabel — [[podwójne nawiasy]]
— jest jedną z bardziej mylących rzeczy w specyfikacji. To sposób wyrażenia tego, co JSON zapisałby jako
tablicę obiektów, i na początku czyta się dziwnie.
# 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" }
# ]}W YAML ta sama struktura czyta się naturalniej — po prostu lista z wciętymi właściwościami.
Dla głęboko zagnieżdżonych danych z powtarzającymi się wpisami tablic, oparte na wcięciu zagnieżdżenie YAML jest
naprawdę mniej rozbudowane niż nagłówki [[sekcji]] TOML. TOML nie ma też odpowiednika systemu
kotwic i aliasów YAML, więc nie możesz raz zdefiniować wspólnego bloku i referować go gdzie indziej. Jeśli
znajdziesz się powielając ten sam zestaw wartości w wielu tabelach TOML, utknąłeś z ręcznym kopiowaniem.
Gdzie każdy format wygrywa w praktyce
YAML wygrywa domyślnie w tych ekosystemach:
- GitHub Actions. Cała składnia przepływu pracy jest YAML. Nie masz tu wyboru i jest to w porządku — wcięcie dobrze mapuje się na zagnieżdżoną strukturę zadań, kroków i warunków.
- Kubernetes. Każdy manifest — Deploymenty, Usługi, ConfigMapy, reguły Ingress — jest YAML. Model obiektów Kubernetes jest głęboko zagnieżdżony, a YAML obsługuje to wdzięcznie.
- Docker Compose. Definicje usług, sieci, woluminy — wszystko YAML. Komentarze wyjaśniające, dlaczego port jest wyeksponowany lub dlaczego używany jest konkretny interwał healthcheck, są częścią dokumentacji.
- Ansible. Playbooki, role, pliki zmiennych — YAML przez cały czas. Obsługa komentarzy jest aktywnie używana do wyjaśniania nieoczywistych parametrów zadań.
TOML wygrywa, gdy kontrolujesz format:
- Projekty Rust.
Cargo.tomlto złoty standard. Deklaracje zależności, flagi funkcji, profile budowania — wszystko w TOML. Jawne typy oznaczają, że ciągi wersji jak"1.0.0"pozostają ciągami. - Projekty Python.
pyproject.tomlstał się standardem dla metadanych projektu Python, konfiguracji budowania i ustawień narzędzi (Black, isort, mypy, pytest wszystkie go czytają). - Konfiguracja narzędzi, gdzie niejednoznaczność powoduje błędy. Jeśli Twoja konfiguracja zawiera ciągi wersji, kody krajów lub inne wartości, które wyglądają jakby mogły być wartościami logicznymi lub liczbami, jawne typy TOML eliminują całą kategorię niespodzianek parsowania.
- Płaskie konfiguracje. Gdy Twoja konfiguracja to głównie pary klucz-wartość na jednym lub dwóch poziomach zagnieżdżenia, TOML jest bardziej czytelny niż YAML i czystszy niż JSON.
Praktyczny przewodnik decyzyjny
Przez większość czasu wybór jest podyktowany przez ekosystem. GitHub Actions to YAML. Kubernetes to YAML. Rust to TOML. Narzędzia Python to TOML. Gdy faktycznie masz wolny wybór, użyj tego jako przewodnika:
- Użyj YAML, gdy narzędzia tego wymagają — platformy CI/CD, Kubernetes, Helm charts, Docker Compose, Ansible. Walka z konwencją kosztuje więcej, niż oszczędza.
- Użyj YAML, gdy potrzebujesz kotwic i aliasów, aby utrzymać złożoną konfigurację DRY — nie ma odpowiednika TOML.
- Użyj TOML dla manifestów projektów i konfiguracji narzędzi, które kontrolujesz — metadane pakietu, ustawienia lintera, konfiguracja budowania.
- Użyj TOML, gdy Twoja konfiguracja zawiera wartości, które YAML 1.1 mógłby błędnie interpretować — ciągi wersji, kody krajów, cokolwiek przypominającego wartość logiczną lub liczbę.
- Cytuj swoje ciągi YAML, gdy wartość może być pomylona z wartością logiczną,
liczbą lub null. Szczególnie: numery wersji, kody krajów, cokolwiek zaczynającego się od cyfry
i wartości takie jak
yes,no,on,off.
Praca z oboma formatami
Chcesz zwalidować lub sformatować plik konfiguracyjny, nad którym pracujesz? TOML Formatter obsługuje pliki TOML, a YAML Formatter obsługuje YAML. Jeśli musisz migrować konfigurację z jednego formatu na inny — na przykład konwertując konfigurację narzędzia opartą na YAML do TOML dla projektu, który go preferuje — konwertery TOML do JSON i YAML do JSON oba wyprowadzają JSON, który następnie możesz konwertować do docelowego formatu. Czasami przejście przez JSON jako pośredni krok jest najbardziej niezawodną ścieżką.
Warto wiedzieć: oba formaty mają własne podnadzbiory kompatybilne z JSON. Prawidłowy JSON jest prawidłowym YAML (YAML jest nadzbiorem JSON). TOML nie ma takiej relacji z JSON, ale specyfikacja TOML jest celowo utrzymana prosto — to krótka lektura, czego nie można powiedzieć o pełnej specyfikacji YAML 1.2.
Podsumowanie
TOML i YAML nie konkurują naprawdę. Osiedliły się w różnych niszach na podstawie swoich priorytetów projektowych. Niejawne typy YAML i struktura oparta na wcięciach czynią go naturalnym dopasowaniem dla dużych, zagnieżdżonych konfiguracji utrzymywanych przez zespoły — pomyśl o manifestach Kubernetes i przepływach pracy GitHub Actions. Jawne typy TOML i płaska struktura sekcji czynią go naturalnym dopasowaniem dla manifestów projektów i konfiguracji narzędzi, gdzie błędnie odczytany ciąg wersji lub kod kraju spowodowałby prawdziwy błąd.
Jedna rzecz, którą warto zapamiętać: jeśli piszesz YAML i masz jakiekolwiek wartości, które
wyglądają jakby mogły być wartościami logicznymi, liczbami lub nullami — cytuj je. To jeden nawyk, który
zapobiega większości błędów konfiguracyjnych związanych z YAML. A jeśli zaczynasz nowy projekt i możesz
swobodnie wybrać format konfiguracyjny, TOML jest wart sprawdzenia.
Repozytorium TOML na GitHub
ma dobre przykłady, a gdy już napiszesz Cargo.toml lub pyproject.toml,
atrakcyjność formatu staje się całkiem jasna.