Problem Norway w YAML i różnice JSON ↔ YAML, które powinien znać inżynier
To był rutynowy deployment przez Helm. Zespół spędził dwa dni na dostosowywaniu pliku values.yaml dla wdrożenia na wiele regionów. Chart szablonował ConfigMap Kubernetes z metadanymi lokalizacyjnymi — w tym kod kraju dla norweskiego centrum danych. Ktoś wpisał country: NO i zacommitował zmianę. Potok CI przeszedł zielono. Deployment trafił na produkcję.
Potem przyszły alerty.
ConfigMap zawierał country: false zamiast country: "NO". Każda usługa downstream, która odczytywała pole country, otrzymywała wartość logiczną zamiast ciągu znaków. Porównanie ciągów zawodziło. Logika routingu przechodziła do wartości domyślnej. Ruch, który powinien pozostać w Norwegii, był przetwarzany przez niewłaściwy regionalny endpoint.
Przyczyną był jeden niecytowany ciąg w pliku YAML. YAML 1.1 — wersja używana przez praktycznie wszystkie narzędzia Kubernetes — traktuje NO jako logiczne false. Traktuje tak samo YES, ON, OFF, Y, N, no, yes, on, off, y, n i wiele innych wariantów. Bez ostrzeżenia. Bez błędu. Cicho błędnie.
JSON nie ma tego problemu. {"country": "NO"} jest zawsze ciągiem znaków. Niejawna koercja typów YAML jest zarówno jego największą wygodą, jak i najgroźniejszą pułapką.
Ten przewodnik omawia pełny obraz: dlaczego problem Norway istnieje, co zmieniło się w YAML 1.2 (i dlaczego większość narzędzi to ignoruje), jak pisać poprawne strategie cytowania, zasady wcięć potykające nowicjuszy, pułapki precyzji liczb i cztery rzeczywiste scenariusze konwersji — od manifestów Kubernetes po plany Terraform. Gdy chcesz bezpiecznie spłaszczyć wartość JSON do YAML bez tej pułapki, nasz konwerter JSON na YAML automatycznie ujmuje w cudzysłowy ciągi podatne na problem Norway.
JSON vs YAML — kiedy używać którego
Zanim zagłębimy się w problem Norway, warto zrozumieć, do czego każdy format jest zoptymalizowany. Nie są wymienne — każdy ma swój punkt docelowy, który czyni go lepszym wyborem w konkretnych kontekstach.
| Wymiar | JSON | YAML |
|---|---|---|
| Składnia | Rygorystyczna — nawiasy, cudzysłowy, przecinki wymagane | Elastyczna — wcięcia, minimalna interpunkcja |
| System typów | Jawny: ciąg, liczba, wartość logiczna, null, tablica, obiekt | Niejawny — YAML 1.1 wnioskuje typy z kształtu wartości |
| Czytelność dla człowieka | Przyjazna dla deweloperów, weryfikowalna maszynowo | Przyjazna dla człowieka, łatwa do edycji ręcznej |
| Wymagania dotyczące cudzysłowów | Ciągi zawsze w cudzysłowach | Większość skalarne może być bez cudzysłowów (źródło Norway) |
| Komentarze | Nieobsługiwane | Obsługiwane przez # |
| Główne zastosowanie | API, wymiana danych, nowoczesne systemy konfiguracji | Kubernetes, Docker Compose, Ansible, potoki CI |
| Zaskakujące parsowanie | Brak — rygorystyczne parsowanie | Tak — Norway, ósemkowe, znaczniki czasu |
| Egzekwowanie schematu | Ekosystem JSON Schema | YAML Schema (mniej narzędzi) |
JSON wygrywa gdy dane przekraczają granice systemów — REST API, kolejki wiadomości, serializacja baz danych. Maszyny to parsują, maszyny to generują, a rygorystyczna składnia ułatwia walidację. Użyj formatowania JSON, aby sprawdzić strukturę przed wysłaniem.
YAML wygrywa gdy głównym autorem są ludzie. Manifesty Kubernetes, przepływy pracy GitHub Actions, Helm chart, Ansible playbook — to pliki, które deweloperzy czytają i edytują dziesiątki razy. Zredukowana interpunkcja i obsługa komentarzy sprawia, że są genuinally bardziej utrzymywalne niż ich odpowiedniki JSON.
Problem pojawia się na granicy: gdy narzędzie generuje JSON (jak kubectl get deploy -o json lub terraform show -json) i człowiek musi przechować lub edytować wynik jako YAML. To właśnie przy tej konwersji żyje problem Norway. Nasz konwerter YAML na JSON obsługuje odwrotny kierunek, gdy chcesz powrócić do JSON.
Problem Norway — dogłębna analiza
Problem Norway nie jest błędem. Jest cechą specyfikacji YAML 1.1 zachowującą się dokładnie zgodnie z projektem. Zrozumienie, dlaczego był tak zaprojektowany — i dlaczego tak wiele systemów nadal implementuje wersję 1.1 — jest kluczem do unikania go.
Dlaczego “no”, “yes”, “on”, “off”, “y”, “n” są błędnie parsowane
Specyfikacja YAML 1.1 zdefiniowała szeroki typ boolowski, który miał być przyjazny dla człowieka. Rozpoznawała wszystkie poniższe jako true lub false:
Prawda: y, Y, yes, Yes, YES, true, True, TRUE, on, On, ON
Fałsz: n, N, no, No, NO, false, False, FALSE, off, Off, OFF
Intencja była dobra: pliki konfiguracyjne często używają yes/no zamiast true/false w języku angielskim, a YAML chciał obsługiwać naturalny sposób pisania konfiguracji przez ludzi. Problem polega na tym, że yes, no, on, off, y i n są też całkowicie uzasadnionymi wartościami ciągów znaków, oznaczającymi coś zupełnie innego w większości aplikacji.
Oto niezgodność w konkretnym YAML:
# YAML 1.1 (co implementuje większość parserów)
country: NO # parsuje jako: country: false ← NIEBEZPIECZNE
enabled: yes # parsuje jako: enabled: true
restart: off # parsuje jako: restart: false
language: y # parsuje jako: language: true
shell: n # parsuje jako: shell: false
# Poprawnie — jawne cudzysłowy ciągów nadpisują wnioskowanie typów
country: "NO" # parsuje jako: country: "NO" ← bezpieczne
enabled: "yes" # parsuje jako: enabled: "yes"
restart: "off" # parsuje jako: restart: "off"
language: "y" # parsuje jako: language: "y"
shell: "n" # parsuje jako: shell: "n"
I porównanie z JSON:
{"country": "NO"}
W JSON NO w cudzysłowach jest zawsze i bezwarunkowo ciągiem znaków. Nie ma niejawnego wnioskowania typów. Rygorystyczność, która sprawia, że JSON wydaje się gadatliwy, jest też tym, co czyni go bezpiecznym.
Poza koercją boolowską YAML 1.1 niejawnie konwertuje też:
123e4→ liczbę1230000(notacja naukowa)0x1A→ liczbę26(szesnastkowa)0755→ liczbę493(ósemkowa — ta łamie ciągi uprawnień pliku Unix)2024-05-04→ obiekt daty w wielu parserach (nie tylko ciąg)1_000_000→ liczbę1000000(separator podkreślenia)
Problem Norway jest tak naprawdę tylko najsławniejszym przedstawicielem całej rodziny niejawnych koercji typów YAML.
YAML 1.1 vs 1.2 — co się zmieniło
YAML 1.2 został opublikowany w 2009 roku — cztery lata po YAML 1.1. Jego głównym celem było ścisłe dostosowanie YAML do JSON (ponieważ JSON jest faktycznie podzbiorem prawidłowego YAML 1.2) i zmniejszenie zaskakujących niejawnych koercji typów.
W YAML 1.2:
- Typ boolowski jest zawężony do dokładnie
trueifalse(z uwzględnieniem wielkości liter). To wszystko.yes,no,on,offsą ciągami gołymi. - Literały ósemkowe wymagają przedrostka
0o(0o755) — stara forma0755jest ciągiem. - Znaczniki czasu nie są niejawnie parsowane —
2024-05-04pozostaje ciągiem, chyba że oznaczysz go jawnie. - Sama specyfikacja jest nadzbiorem JSON, co oznacza, że każdy prawidłowy dokument JSON jest prawidłowym YAML 1.2.
Na papierze YAML 1.2 całkowicie rozwiązuje problem Norway. W praktyce ekosystem prawie się nie poruszył.
| Biblioteka | Domyślna spec | Ryzyko Norway |
|---|---|---|
| PyYAML (Python) | YAML 1.1 | Tak — yaml.safe_load nadal parsuje NO jako False |
| ruamel.yaml (Python) | YAML 1.2 (opcjonalnie) | Konfigurowalne — domyślnie bezpieczniejsze |
| js-yaml (Node.js) | YAML 1.1 | Tak w starszych wersjach; nowsze mają opcję FAILSAFE_SCHEMA |
| eemeli/yaml (Node.js) | YAML 1.2 | Nie — domyślnie 1.2, z możliwością jawnego wyboru wersji |
| gopkg.in/yaml.v2 (Go) | YAML 1.1 | Tak |
| gopkg.in/yaml.v3 (Go) | YAML 1.2 | Znacznie bezpieczniejsze |
| Kubernetes / Helm | YAML 1.1 (przez Go yaml.v2) | Tak — historyczne, bardzo trudne do migracji |
| Ansible | YAML 1.1 (przez PyYAML) | Tak |
Powodem powolnej migracji jest wsteczna kompatybilność. Systemy, które przez dziesięciolecia polegały na parsowaniu yes/no jako wartości logicznych, nie mogą cicho zmienić tego zachowania bez łamania istniejących konfiguracji. Kubernetes w szczególności jest masową bazą instalacyjną, w której zmiana semantyki parsowania YAML byłaby zmianą łamiącą wsteczną kompatybilność na poziomie klastra.
Praktyczny wniosek: zakładaj semantykę YAML 1.1 w każdym narzędziu, którego nie skonfigurowałeś jawnie inaczej. Zawsze ujmuj w cudzysłowy ciągi, które mogłyby być błędnie odczytane jako wartości logiczne, znaczniki czasu lub liczby.
Jak systemy produkcyjne wpadają w pułapkę
Kod kraju Norwegii jest najczęściej przytaczanym przykładem, bo jest kontraintucyjny — NO wygląda jak oczywisty skrót, nie jak wartość logiczna. Ale wzorzec powtarza się w wielu rzeczywistych scenariuszach:
Kody lotnisk IATA. Norweskie lotnisko Harstad/Narvik ma kod EVE. Bezpieczny. Oslo Gardermoen to OSL. Też bezpieczny. Ale każda aplikacja używająca YAML do przechowywania regionalnych kodów lotnisk jest o jeden kod trasy no od logicznego false na produkcji.
Wartości zmiennych środowiskowych. ON jest całkowicie prawidłową wartością zmiennej środowiskowej oznaczającą „włączone“ w niektórych starszych systemach. OFF jest jej odpowiednikiem. Migracja konfiguracji ze skryptów shell do YAML bez cytowania tych wartości wprowadza cichą koercję typów.
Pola użytkownika w e-mailach. Użytkownik, którego imię lub nazwa użytkownika to dosłownie n, y lub dowolne ze słów-kluczy, zostanie błędnie zserializowany, jeśli aplikacja zrzuca YAML bez właściwego cytowania. Jest to szczególnie podstępne, bo zawodzi tylko dla podzbioru użytkowników.
Polityki restartowania Docker Compose. Wartość "no" pola restart_policy oznacza „nie restartuj“. Jeśli straci cudzysłowy w konwersji YAML w obie strony, wartość staje się false, a Docker Compose może to zinterpretować jako „nie określono polityki restartowania“ lub rzucić błąd walidacji — tak czy inaczej, zachowanie restartu kontenera jest błędne.
Pole shell: w GitHub Actions. Prawidłowe wartości shell to bash, pwsh, python, sh, cmd, powershell. Żadna z nich nie jest słowem Norway. Ale ktoś, kto wpisze shell: yes lub shell: on jako placeholder podczas roboczej edycji, może być zaskoczony, gdy YAML zamieni to na wartość logiczną, zanim walidator w ogóle to zobaczy.
Rozwiązanie jest we wszystkich przypadkach takie samo: ujmuj w cudzysłowy ciągi, które semantycznie są ciągami, niezależnie od tego czy człowiek rozpozna je jako słowa kluczowe. Nasz konwerter JSON na YAML robi to automatycznie — każda wartość z listy słów Norway jest ujmowana w cudzysłowy w wynikowym pliku.
Strategia cytowania ciągów
Gdy rozumiesz już, dlaczego słowa Norway powodują niezgodność, rozwiązaniem jest wybór odpowiedniej strategii cytowania dla konkretnego zastosowania. YAML obsługuje trzy tryby, każdy z innymi kompromisami.
Auto vs Podwójne vs Pojedyncze
Cytowanie Auto (zalecane dla większości konwersji) pozwala bibliotece zdecydować, kiedy cudzysłowy są konieczne. Wartości, które byłyby błędnie odczytane bez cudzysłowów — słowa Norway, liczby, znaczniki czasu, ciągi wyglądające jak składnia YAML — są automatycznie ujmowane w cudzysłowy. Wszystko inne pozostaje jako skalar gołt. Daje to najbardziej czytelne wyjście przy zachowaniu bezpieczeństwa.
# Wyjście w trybie Auto
name: Alice # goły — bez niejednoznaczności
country: "NO" # cytowany — słowo Norway
age: 30 # goły — jednoznaczna liczba
created: "2024-05-04" # cytowany — inaczej parsowałby jako data
port: "8080" # zależy od biblioteki — niektóre cytują ciągi wyglądające jak liczby
Ciągi w podwójnych cudzysłowach owijają wszystkie wartości ciągów w cudzysłowy podwójne. Jest to jawne i czytelne — każdy czytelnik widzi, że wszystkie te wartości są ciągami, bez rozumowania o specyfikacji. Kompromisem jest gadatliwość i zmniejszona czytelność dla człowieka, szczególnie przy głęboko zagnieżdżonych konfiguracjach.
# Tryb Podwójne
name: "Alice"
country: "NO"
replicas: "3" # nawet liczby stają się ciągami — może powodować błędy schematu
Uważaj: jeśli docelowy schemat oczekuje liczby, a ty serializujesz ją jako cytowany ciąg, parser YAML poprawnie oznaczy ją typem ciąg, ale Kubernetes lub inny rygorystyczny konsument może odrzucić pole jako błędny typ.
Ciągi w cudzysłowach pojedynczych to funkcja specyficzna dla YAML — JSON nie ma składni z apostrofami. Cudzysłowy pojedyncze są dosłowne: wewnątrz nich nie ma sekwencji escape. Jedynym przypadkiem specjalnym jest to, że apostrof wewnątrz ciągu w apostrofach musi być podwojony (''). Cudzysłowy pojedyncze są idealne dla ciągów zawierających ukośniki wsteczne lub znaki specjalne wymagające escape w podwójnych cudzysłowach.
# Tryb Pojedyncze
pattern: 'C:\Users\alice\Documents' # bez potrzeby escape
regex: '\d+\.\d+' # ukośniki dosłowne
Dla konwersji JSON do YAML przeznaczonych do konwersji w obie strony z powrotem do JSON preferuj tryb Auto lub Podwójne. Ciągi w apostrofach wprowadzają składnię specyficzną dla YAML, wymagającą parsera obsługującego YAML podczas powrotu.
Skalarne blokowe (| i >)
Składnia blokowych skalarne YAML jest genuinally przydatna dla ciągów wieloliniowych — czymś, czym JSON radzi sobie niezręcznie, używając sekwencji escape \n.
Literalny skalar blokowy | zachowuje znaki nowej linii dokładnie:
# Blok literalny — znaki nowej linii zachowane
script: |
#!/bin/bash
set -euo pipefail
echo "Starting deployment"
kubectl apply -f manifest.yaml
# Równoważna reprezentacja JSON (nieczytelna)
# {"script": "#!/bin/bash\nset -euo pipefail\necho \"Starting deployment\"\nkubectl apply -f manifest.yaml\n"}
Złożony skalar blokowy > łączy linie spacjami, zamieniając każdy znak nowej linii w spację (z wyjątkiem pustych linii, które stają się znakami nowej linii):
# Blok złożony — znaki nowej linii stają się spacjami
description: >
This service handles authentication
for the entire platform. It supports
OAuth2, SAML, and API key authentication.
# Wynik: "This service handles authentication for the entire platform. It supports OAuth2, SAML, and API key authentication.\n"
Skalarne blokowe sprawdzają się przy osadzaniu certyfikatów TLS, wieloliniowych skryptów shell lub zapytań SQL w konfiguracjach YAML — scenariuszach, gdzie odpowiednik JSON byłby długim, escapowanym, jednolinijkowym tekstem, którego żaden człowiek nie może czytać.
Przy konwersji z JSON do YAML większość konwerterów (w tym nasz) używa trybu Auto i reprezentuje ciągi wieloliniowe blokowymi skalarnymi tylko gdy wykryje osadzone znaki nowej linii. Ciągi jednoliniowe otrzymują skalarne przepływowe (cytowane lub gołe). Użyj naszego konwertera JSON na YAML, aby zobaczyć wynik przed zacommitowaniem go do manifestu.
Wcięcia — 2 vs 4 spacje, tabulatory zabronione
Reguły wcięć YAML są bardziej rygorystyczne, niż wyglądają. Specyfikacja ma jedną absolutną regułę i jedną konwencję, która różni się w zależności od ekosystemu.
Absolutna reguła: tabulatory są zabronione. Każdy poziom wcięcia musi używać spacji. Znak tabulacji w pliku YAML jest błędem parsowania w większości parserów:
# BŁĘDNIE — tabulatory powodują błędy parsowania
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app # ← znak tabulacji tutaj → ParseError
# POPRAWNIE — tylko spacje
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app # ← dwie spacje
Komunikat o błędzie różni się w zależności od biblioteki. W PyYAML Pythona:
yaml.scanner.ScannerError: while scanning for the next token
found character '\t' that cannot start any token
W yaml.v3 Go:
yaml: line 4: found character that cannot start any token
Skonfiguruj edytor, aby rozszerzał tabulatory do spacji dla plików YAML. W VS Code dodaj do ustawień workspace: "[yaml]": { "editor.insertSpaces": true, "editor.tabSize": 2 }.
Konwencja: 2 vs 4 spacje. Obie są prawidłowe. Konwencje ekosystemu różnią się:
| Ekosystem | Konwencja | Powód |
|---|---|---|
| Manifesty Kubernetes | 2 spacje | Oficjalna dokumentacja i przykłady używają 2 |
| Helm chart | 2 spacje | Podąża za konwencją K8s |
| Docker Compose | 2 spacje | Oficjalne przykłady specyfikacji compose |
| GitHub Actions | 2 spacje | Oficjalne przykłady przepływów pracy |
| Ansible playbook | 2 spacje | Oficjalna dokumentacja |
| Tradycyjne konfiguracje | 4 spacje | Odpowiada domyślnemu formatowaniu JSON |
Dla każdego pliku, który będzie używany przez Kubernetes lub Docker Compose, używaj 2 spacji. Dla samodzielnych plików konfiguracyjnych, które będą czytane tylko przez ludzi i niestandardowe narzędzia, obie możliwości działają — zachowaj tylko spójność w pliku. Nasz konwerter JSON na YAML domyślnie używa 2-spacjowych wcięć i pozwala przełączyć się na 4 dla projektów, które to preferują.
Jeszcze jedna zasada: elementy potomne muszą być wcięte bardziej niż ich rodzic, ale liczba dodatkowych spacji może być dowolną liczbą dodatnią (1, 2, 3, 4…) — o ile jest spójna w obrębie bloku. W praktyce zawsze używaj 2 lub 4 dla czytelności.
Obsługa liczb w JSON ↔ YAML
Oba formaty obsługują liczby, ale przypadki brzegowe różnią się wystarczająco, by powodować błędy produkcyjne.
Utrata precyzji dla dużych liczb
Typ Number JavaScript to 64-bitowa liczba zmiennoprzecinkowa IEEE 754. Może dokładnie reprezentować liczby całkowite do 2^53 − 1 = 9 007 199 254 740 991. Powyżej tego precyzja liczb całkowitych jest tracona:
// Utrata precyzji JavaScript — to nie jest problem YAML, ale wpływa na parsowanie JSON
JSON.parse('{"v": 9007199254740993}').v
// → 9007199254740992 (3 stało się 2 — jeden bit stracony)
// Bezpieczne — w zakresie 2^53
JSON.parse('{"v": 9007199254740991}').v
// → 9007199254740991 (dokładne)
Ma to znaczenie dla konwersji JSON do YAML w środowiskach JavaScript, ponieważ precyzja jest już tracona przed rozpoczęciem serializacji YAML. Kubernetes metadata.resourceVersion jest polem ciągowym specjalnie dlatego, że wersje zasobów mogą przekraczać bezpieczny zakres liczb całkowitych. Inne pola wyglądające jak małe liczby — observedGeneration, komponenty uid — są bezpieczniejsze, ale każde pole int64 w odpowiedzi K8s jest potencjalnie dotknięte.
Obejścia:
- Używaj Pythona lub Go dla potoków konwersji obejmujących duże liczby — oba obsługują arbitralne liczby całkowite natywnie.
- W Node.js używaj parsera JSON obsługującego BigInt:
JSON.parse(text, (_, v) => typeof v === 'number' && !Number.isSafeInteger(v) ? BigInt(v) : v). - Dla pól wymagających precyzji konwersji w obie strony bez strat serializuj je jako ciągi znaków w źródle.
- Przy przeglądaniu przekonwertowanego YAML sprawdzaj pola takie jak
resourceVersion,generationi wartości pochodne od znaczników czasu.
Osobliwości ósemkowe i szesnastkowe
YAML 1.1 traktuje niektóre ciągi podobne do liczb jako liczby całkowite niedziesiętne:
# Niespodzianki parsowania YAML 1.1
permissions: 0755 # parsuje jako ósemkowe 493, nie dziesiętne 755
value: 0x1A # parsuje jako szesnastkowe 26, nie ciąg "0x1A"
# Zachowanie YAML 1.2
permissions: 0755 # pozostaje jako całkowita 755 (dziesiętna) — ósemkowe wymaga przedrostka 0o
permissions: 0o755 # parsuje jako ósemkowe 493 zarówno w 1.1, jak i 1.2
# Bezpieczne dla obu spec — cytuj każdą wartość z wiodącymi zerami
permissions: "0755" # zawsze ciąg "0755"
Pułapka ósemkowa jest szczególnie niebezpieczna dla uprawnień pliku Unix, komponentów adresów IP z wiodącymi zerami (niektóre urządzenia sieciowe) i wszelkich kodów numerycznych używających wiodących zer do wyrównania (kody pocztowe, kody produktów). Zawsze cytuj te wartości pisząc YAML ręcznie lub upewnij się, że konwerter je cytuje — nasz konwerter JSON na YAML wykrywa ciągi numeryczne z JSON i zachowuje ich typ ciągu.
Rzeczywiste konwersje
Problem Norway i strategie cytowania stają się konkretne, gdy zastosuje się je do rzeczywistych scenariuszy konwersji.
Manifest Kubernetes z JSON
Kanonicznym przepływem pracy jest: kubectl get deploy my-app -o json daje żywy obiekt jako JSON. Chcesz go oczyścić (usunąć status, creationTimestamp, zarządzane pola) i zacommitować do git jako manifest YAML.
Źródłowy JSON (skrócony):
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "my-app",
"namespace": "production",
"labels": {
"app": "my-app",
"region": "NO"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": { "app": "my-app" }
},
"template": {
"spec": {
"containers": [{
"name": "app",
"image": "registry.example.com/my-app:v1.2.3",
"env": [
{ "name": "REGION", "value": "NO" },
{ "name": "ENABLE_FEATURE", "value": "yes" }
]
}]
}
}
}
}
Oczekiwane wyjście YAML (z ochroną Norway):
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production
labels:
app: my-app
region: "NO" # cytowany — słowo Norway
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
spec:
containers:
- name: app
image: registry.example.com/my-app:v1.2.3
env:
- name: REGION
value: "NO" # cytowany — słowo Norway
- name: ENABLE_FEATURE
value: "yes" # cytowany — słowo Norway
Zauważ, że replicas: 3 jest pozostawione bez cudzysłowów — jest to uzasadniona liczba całkowita, której Kubernetes oczekuje jako liczby. Słowa Norway w labels i wartościach env są cytowane. Prosty konwerter nieobsługujący boolowskich YAML 1.1 cicho wyprodukowałby region: false i value: false.
Po konwersji zweryfikuj przez: kubectl apply --dry-run=client -f manifest.yaml. To wyłapuje błędy schematu bez dotykania klastra.
Wypróbuj konwersję w naszym konwerterze JSON na YAML — wklej powyższy JSON i natychmiast zobaczysz bezpieczne wyjście dla Norway. Użyj naszego konwertera YAML na JSON, aby zweryfikować konwersję w obie strony.
Docker Compose z JSON
Potoki CI/CD czasem generują konfiguracje Docker Compose programowo z magazynu konfiguracji JSON, a następnie zapisują je na dysku jako YAML, by mogli je czytać deweloperzy.
Krytyczna pułapka — polityka restartowania:
{"restart_policy": "no"}
W Compose restart_policy: "no" jest prawidłową wartością oznaczającą „nigdy nie restartuj kontenera“. Bez cudzysłowów w YAML staje się restart_policy: false, co Docker Compose może traktować jako tę samą semantykę (falsy = brak restartu) lub odrzucić z błędem walidacji typów — zachowanie różni się w zależności od wersji Compose. Cytowanie jest obowiązkowe.
Uważaj też na: Compose v3 deploy.restart_policy.condition: "on-failure" — wartość on-failure zawiera słowo on, ale jest z łącznikiem i nie jest na liście wyzwalaczy, więc jest faktycznie bezpieczna. Jednak condition: on (bez -failure) spowodowałoby niezgodność. Cytuj wartości zmiennych środowiskowych w bloku environment:, jeśli mogłyby być słowami Norway.
Waliduj pliki Compose po konwersji: docker-compose config parsuje i ponownie wypisuje kanoniczną formę, ujawniając błędy typów.
Przepływ pracy GitHub Actions
Przepływy pracy GitHub Actions to pliki YAML edytowane ręcznie przez deweloperów. Najczęstszym scenariuszem konwersji jest odczytanie danych przepływu pracy z GitHub API (które zwraca JSON) i konwersja do lokalnego pliku YAML do edycji.
Kluczowe pola do sprawdzenia:
# BEZPIECZNE — brak słów Norway w standardowych GitHub Actions
on: # "on" jako klucz YAML tutaj, nie wartość — obsługiwane inaczej
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
npm install
npm test
env:
NODE_ENV: production # bezpieczne — nie jest słowem Norway
DEBUG: "off" # słowo Norway w wartości — wymaga cytowania
Uwaga: on: jako klucz YAML jest specjalny — problem Norway dotyczy wartości, nie kluczy. Ale on jako wartość (jak DEBUG: on) wyzwoli koercję. Blok env: zasługuje na szczególną uwagę, ponieważ wartości zmiennych środowiskowych są ciągami, ale wiele z nich to krótkie flagi mogące kolidować ze słowami Norway.
Dla przepływów pracy zawierających specyfikacje shell:, prawidłowe wartości (bash, pwsh, sh, python) są wszystkie bezpieczne od koercji Norway. Wartości niestandardowe powinny być cytowane profilaktycznie.
Plan JSON Terraform → YAML
terraform show -json tfplan > plan.json wypisuje szczegółową reprezentację JSON tego, co Terraform planuje stworzyć, zmodyfikować lub zniszczyć. Konwersja do YAML czyni ją bardziej czytelną dla przeglądów pull request i audytów zgodności.
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
# Następnie konwertuj naszym narzędziem lub biblioteką
Plan JSON Terraform jest złożony i głęboki. Kluczowe obawy przy konwersji:
-
Duże identyfikatory całkowite. Identyfikatory zasobów chmury (ID kont AWS, numery projektów GCP) i obliczone wartości atrybutów mogą być dużymi liczbami. Konwertuj przez Pythona lub Go, aby uniknąć utraty precyzji float64.
-
Ciągi ograniczeń wersji. Terraform używa
~>,>=,<=w ograniczeniach wersji dostawcy. Są to wartości ciągów, które YAML obsługuje poprawnie, o ile nie są słowami Norway —~>jest bezpieczne. -
Wartości konfiguracji dostawcy. Wyniki planów Terraform mogą zawierać wartości konfiguracji dla zasobów. Jeśli pole boolowskie domyślnie
falsejest reprezentowane jako"no"w schemacie jakiegoś dostawcy, to jest ryzyko Norway przy powrocie do YAML. -
Blok
.sensitive_values. Wrażliwe wartości są redagowane jako wartości logicznetruew planie JSON. Te przechodzą konwersję czysto, ponieważtruenie jest słowem Norway w żadnej wersji YAML.
Konwersja Terraform do YAML jest głównie dla przeglądu przez człowieka, nie dla podawania z powrotem do Terraform. Nie używaj manifestów YAML jako danych wejściowych Terraform — natywny format Terraform to HCL, a jego format wejściowy JSON jest specyficzny i osobno udokumentowany.
Przykłady kodu — 4 języki
Node.js (eemeli/yaml + js-yaml)
Ekosystem Node.js ma dwie dominujące biblioteki YAML ze znacząco różną obsługą Norway:
// eemeli/yaml — zalecana, YAML 1.2 domyślnie, bezpieczna dla Norway
import { stringify } from 'yaml';
import { readFileSync } from 'fs';
const jsonInput = readFileSync('input.json', 'utf8');
const data = JSON.parse(jsonInput);
// Domyślnie: YAML 1.2 — "NO" pozostaje jako "NO", bez koercji boolowskiej
const yamlOutput = stringify(data);
console.log(yamlOutput);
// region: NO ← bezpieczne w 1.2, ale dla maksymalnej kompatybilności cytuj jawnie
// Wymuszenie zachowania YAML 1.1 (dla środowisk K8s/Helm parsujących 1.1)
const yamlForK8s = stringify(data, { version: '1.1' });
// region: 'NO' ← automatycznie cytowany, bo 1.1 parsowałoby NO jako false
console.log(yamlForK8s);
// js-yaml — powszechna, ale semantyka YAML 1.1, ryzykowna bez ostrożności
import yaml from 'js-yaml';
import { readFileSync } from 'fs';
const data = JSON.parse(readFileSync('input.json', 'utf8'));
// Domyślny dump — słowa Norway mogą nie być cytowane
const unsafe = yaml.dump(data);
// region: NO ← parsowany jako false, jeśli ponownie odczytany przez parser 1.1!
// Bezpieczniej: użyj niestandardowego schematu lub wymuś cytowanie
const safer = yaml.dump(data, {
schema: yaml.JSON_SCHEMA, // ogranicza do typów zgodnych z JSON
noCompatMode: false,
lineWidth: -1,
quotingType: '"',
forceQuotes: false, // cytuje tylko gdy konieczne według schematu JSON
});
W nowych projektach preferuj eemeli/yaml. Jej domyślny YAML 1.2 jest bezpieczniejszy, jej Document API daje szczegółową kontrolę nad cytowaniem i lepiej obsługuje wierność konwersji w obie strony. Dla projektów już używających js-yaml użyj opcji JSON_SCHEMA, aby ograniczyć do typów bezpiecznych dla JSON.
Python (PyYAML + ruamel.yaml)
Python jest dominującym językiem dla narzędzi Kubernetes, Ansible i potoków inżynierii danych — wszyscy intensywni użytkownicy YAML.
import json
import yaml
import sys
# PyYAML — prosty, standardowy, ale domyślnie YAML 1.1
with open('input.json') as f:
data = json.load(f)
output = yaml.dump(data, default_flow_style=False, allow_unicode=True)
# country: 'NO' ← PyYAML jest na tyle sprytny, by automatycznie cytować słowa Norway
# Ale nie cytuje "yes", "no" (małe litery) we wszystkich konfiguracjach:
# enabled: 'yes' ← cytowany
# tag: y ← może lub nie być cytowany w zależności od wersji
print(output)
import json
import sys
from ruamel.yaml import YAML
# ruamel.yaml — wierność konwersji w obie strony, obsługuje YAML 1.2, zalecana na produkcji
yaml_rt = YAML()
yaml_rt.default_flow_style = False
yaml_rt.width = 4096 # zapobiega niechcianemu zawijaniu linii
yaml_rt.best_map_flow_style = False
with open('input.json') as f:
data = json.load(f)
yaml_rt.dump(data, sys.stdout)
# Zachowuje kolejność kluczy, poprawnie obsługuje słowa Norway, obsługuje kotwice przy konwersji w obie strony
Dla skryptów automatyzacji Ansible i Kubernetes konwertujących odpowiedzi JSON API do manifestów YAML, ruamel.yaml jest bezpieczniejszym wyborem. PyYAML jest w porządku dla prostych skryptów, gdzie kontrolujesz dane wejściowe i sprawdziłeś, że nie pojawiają się słowa Norway.
Go (gopkg.in/yaml.v3)
Go jest językiem samego ekosystemu Kubernetes — kubectl, Helm, Argo, Flux i większość operatorów K8s są napisane w Go.
package main
import (
"encoding/json"
"fmt"
"os"
"gopkg.in/yaml.v3"
)
func main() {
// Odczyt wejścia JSON
jsonBytes, err := os.ReadFile("input.json")
if err != nil {
panic(err)
}
// Deserializacja JSON do ogólnej mapy
var data map[string]interface{}
if err := json.Unmarshal(jsonBytes, &data); err != nil {
panic(err)
}
// Serializacja do YAML — yaml.v3 używa semantyki YAML 1.2
yamlBytes, err := yaml.Marshal(data)
if err != nil {
panic(err)
}
fmt.Println(string(yamlBytes))
// country: "NO" ← yaml.v3 poprawnie cytuje słowa Norway
// replicas: 3 ← liczby całkowite pozostają liczbami całkowitymi
// enabled: true ← wartości logiczne pozostają wartościami logicznymi
}
yaml.v3 jest znaczącym ulepszeniem względem yaml.v2 pod kątem bezpieczeństwa Norway. Biblioteka v2 stosowała YAML 1.1 i pisała NO bez cudzysłowów; v3 poprawnie cytuje niejednoznaczne wartości. Jeśli utrzymujesz starszy projekt Go używający v2, zaktualizuj do v3 — API jest w dużej mierze kompatybilne, a poprawa bezpieczeństwa warta jest migracji.
Dla bezpiecznej typowo konwersji ze strukturami Go (zamiast map[string]interface{}), użyj tagów struktury:
type DeploymentLabels struct {
App string `yaml:"app" json:"app"`
Region string `yaml:"region" json:"region"`
}
// yaml.Marshal na polu struktury zawierającym "NO" poprawnie je cytuje w v3
Bash CLI (yq + jq)
Dla skryptów shell i szybkich jednorazowych konwersji yq (wersja Mike’a Faraha, mikefarah/yq) konwertuje JSON do YAML jednym poleceniem:
# Instalacja yq
brew install yq # macOS
sudo wget -qO /usr/local/bin/yq \
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
chmod +x /usr/local/bin/yq # Linux
# Konwersja pliku JSON do YAML
yq -P < input.json > output.yaml
# Konwersja z wynikowego JSON kubectl
kubectl get deploy my-app -o json | yq -P > manifest.yaml
# Najpierw przekaz przez jq do filtrowania/transformacji, potem konwertuj do YAML
kubectl get deploy my-app -o json \
| jq 'del(.status, .metadata.creationTimestamp, .metadata.managedFields)' \
| yq -P > clean-manifest.yaml
Potok jq | yq to potężny wzorzec: użyj jq do manipulacji JSON (filtrowanie pól, zmiana kształtu struktury, zapytania o wartości) i yq -P jako końcowego serializatora YAML.
Ostrzeżenie Norway z yq: yq (mikefarah) respektuje typ wejściowy z JSON — ciąg JSON "NO" w danych wejściowych zostanie zserializowany jako ciąg YAML z cudzysłowami. Ale jeśli generujesz YAML bezpośrednio przez yq (nie z danych wejściowych JSON), musisz jawnie cytować wartości będące słowami Norway. Użyj naszego konwertera YAML na JSON, aby zweryfikować konwersję w obie strony po wynikowym yq.
Przypadki brzegowe i pułapki
Poza problemem Norway konwersja JSON ↔ YAML ma kilka przypadków brzegowych, które potykają doświadczonych inżynierów:
-
Wielodokumentowy YAML (separator
---). Jeden plik YAML może zawierać wiele dokumentów oddzielonych---. JSON nie ma równoważnej koncepcji. Przy konwersji wielodokumentowego YAML do JSON większość narzędzi albo bierze tylko pierwszy dokument, łączy wszystkie dokumenty w tablicę, albo zgłasza błąd. Przy konwersji JSON do YAML dodawany jest jeden nagłówek dokumentu---zgodnie z konwencją. Jawnie określ i udokumentuj swoje zachowanie dla potoków, które mogą napotkać pliki wielodokumentowe. -
Kotwice i aliasy YAML. YAML obsługuje definicje
&anchori referencje*aliasdla konfiguracji DRY. Przy konwersji YAML do JSON kotwice muszą być rozwinięte — wynikowy JSON może być znacznie większy niż źródłowy YAML. Przy konwersji JSON do YAML konwerter nie może odtworzyć kotwic, które nie istniały w oryginale. Aliasy są funkcją wyłącznie YAML. -
Niejawne parsowanie znaczników czasu. Parsery YAML 1.1 konwertują
2024-05-04i2024-05-04T12:00:00Zdo obiektów daty natywnych dla języka, nie do ciągów. Gdy ten obiekt daty jest serializowany z powrotem do JSON, wynik zależy od biblioteki: niektóre wypisują ciągi ISO, niektóre znaczniki czasu Unix, niektóre null. Konwersja dat przez YAML bez jawnego cytowania ciągów ("2024-05-04") może cicho zmienić format. -
Tag
!!binary. YAML może osadzać dane binarne zakodowane w Base64 z tagiem!!binary. JSON nie ma typu binarnego — dane binarne muszą być ciągiem Base64. Przy konwersji YAML z polami!!binarydo JSON dekoduj do ciągu Base64. Przy konwersji z powrotem nie możesz odtworzyć tagu binarnego bez znajomości schematu. Kubernetes używa!!binarydla niektórych wartości sekretów. -
Kolizje typów kluczy. JSON wymaga, aby klucze obiektów były ciągami. YAML pozwala na klucze dowolnego typu — klucze całkowite, boolowskie, a nawet złożone obiekty jako klucze. Plik YAML z
true: valuelub1: valuejako kluczami nie może być wiernie przedstawiony jako JSON. Większość konwerterów przekształca klucze w ciągi, ale semantyka się zmienia. -
Wariancja reprezentacji null. W YAML
null,~,Null,NULLi pusta wartość oznaczają wszystkie null. W JSON tylkonulljest null. Przy konwersji YAML do JSON wszystkie te formy normalizują się donull. Ale przy konwersji JSON z powrotem do YAML wybór reprezentacji null ma znaczenie —~jest bardziej zwarte,nulljest bardziej jawne. Wybierz jeden i trzymaj się go. -
Zmiany porządku sortowania. Obiekty JSON technicznie nie mają zdefiniowanej kolejności kluczy (choć większość parserów zachowuje kolejność wstawiania). Odwzorowania YAML podobnie nie mają wymaganej kolejności. Ale niektóre biblioteki YAML sortują klucze alfabetycznie domyślnie przy serializacji. Może to powodować duże diffy w systemie kontroli wersji, jeśli źródłowy JSON używał innej kolejności. Skonfiguruj
sort_keys=Falsew PyYAML i równoważne opcje w innych bibliotekach.
Kiedy NIE konwertować
Konwersja nie zawsze jest właściwą odpowiedzią. Oto scenariusze, w których pozostanie w oryginalnym formacie jest lepszym wyborem:
Nie konwertuj YAML do JSON, jeśli YAML zawiera komentarze dokumentujące logikę biznesową. Komentarze YAML nie są częścią modelu danych — znikają przy każdej serializacji do JSON. Jeśli manifest Kubernetes zawiera komentarze wyjaśniające, dlaczego wybrano konkretny limit zasobów lub dlaczego zrobiono wyjątek polityki bezpieczeństwa, konwersja do JSON niszczy tę dokumentację. Zachowaj YAML.
Nie auto-konwertuj konfiguracji w potokach CI bez testów konwersji w obie strony. Jeśli potok konwertuje JSON do YAML, a następnie stosuje YAML do klastra, dodaj krok walidacji konwersji w obie strony: YAML z powrotem do JSON, a następnie porównaj z oryginałem. To wyłapuje niespodzianki koercji typów przed dotarciem do produkcji.
Nie konwertuj tylko dlatego, że narzędzie wypisuje JSON. kubectl, aws, terraform i docker inspect wszystkie wypisują JSON, ale większość z tych narzędzi akceptuje też YAML jako dane wejściowe. Przed zbudowaniem kroku konwersji sprawdź, czy docelowe narzędzie może bezpośrednio przyjąć dane wejściowe YAML — większość nowoczesnych narzędzi DevOps może. Nasz konwerter YAML na JSON jest najbardziej przydatny, gdy konkretnie potrzebujesz JSON dla narzędzia nieakceptującego YAML.
Nie konwertuj, jeśli schematy się różnią. Jeśli JSON używa kluczy camelCase, a konsument YAML oczekuje snake_case (lub odwrotnie), potrzebujesz kroku transformacji oprócz konwersji formatu. Czysta konwersja formatu da syntaktycznie poprawny, ale semantycznie błędny YAML. Zajmij się jawnie przyporządkowaniem schematu.
Nie utrzymuj obu formatów synchronicznie ręcznie. Jeśli utrzymujesz config.json i config.yaml, które mają być równoważne, dojdzie do rozbieżności. Wybierz jeden kanonoiczny format i automatycznie z niego wyprowadzaj drugi — lub lepiej, wybierz jeden format i wyeliminuj duplikację.
FAQ
Czy problem Norway w YAML nadal dotyczy nowoczesnych systemów?
Tak — jest wszechobecny w ekosystemie. Kubernetes i Helm używają biblioteki yaml.v2 Go (semantyka YAML 1.1) w znacznych częściach swoich baz kodu. Ansible używa PyYAML (YAML 1.1). Przepływy pracy GitHub Actions są parsowane przez wewnętrzny parser YAML GitHub, który ma własne zachowanie. Większość plików YAML CI/CD w praktyce jest przetwarzana przez parsery YAML 1.1. Zakładaj semantykę 1.1, dopóki nie sprawdzisz inaczej.
Dlaczego miałbym konwertować JSON do YAML, jeśli YAML jest trudniejszy do parsowania?
Konwersja nie dotyczy trudności parsowania — chodzi o możliwość edycji przez człowieka. JSON jest idealny dla maszyn; YAML jest idealny dla ludzi, którzy muszą czytać, edytować i przeglądać pliki konfiguracyjne. Manifest Kubernetes zacommitowany do git, przeglądany w pull requestach i ręcznie dostrajany przez inżynierów powinien być w YAML. Ten sam manifest pobrany z API do programowego przetwarzania powinien być w JSON. Nasz konwerter JSON na YAML stanowi pomost między tymi dwoma.
Czy konwersja JSON ↔ YAML w obie strony jest bezstratna?
Z zastrzeżeniami — tak, dla danych zgodnych z JSON. JSON jest podzbiorem YAML 1.2, więc każdy prawidłowy dokument JSON jest prawidłowym YAML 1.2. Przejście JSON → YAML → JSON powinno być bezstratne dla danych bez niejawnej koercji typów. Problem Norway oznacza, że ciąg JSON "NO" mógłby przetrwać przejście do przodu tylko jeśli konwerter go cytuje, i przetrwać przejście powrotne tylko jeśli parser YAML respektuje cudzysłowy. Używaj biblioteki YAML 1.2 dla obu kierunków, aby zagwarantować bezstratne konwersje w obie strony.
Która biblioteka YAML jest najlepsza na produkcję?
Dla Pythona: ruamel.yaml skonfigurowana dla YAML 1.2. Dla Node.js: eemeli/yaml (pakiet yaml na npm). Dla Go: gopkg.in/yaml.v3. Wszystkie trzy implementują semantykę YAML 1.2 lub mają jawne tryby YAML 1.2 i poprawnie obsługują słowa Norway. Unikaj bibliotek YAML 1.1 w nowych projektach. Jeśli z powodów kompatybilności musisz używać biblioteki 1.1 (PyYAML, js-yaml, yaml.v2), zawsze jawnie cytuj ciągi podatne na problem Norway.
Czy manifesty YAML Kubernetes obsługują komentarze po konwersji z JSON?
Nie — komentarzy nie można odtworzyć z JSON. JSON nie ma składni komentarzy, więc nie ma nic do konwersji. Gdy uruchamiasz kubectl get deploy -o json i konwertujesz wynik do YAML do przechowywania w git, wynikowy YAML nie będzie miał komentarzy. Komentarze w manifeście Kubernetes muszą być napisane przez człowieka po konwersji. To jeden z powodów, dla których zachowanie ręcznie napisanego YAML jako kanonicznego źródła jest często korzystniejsze niż konwersja w obie strony przez API JSON.
Jak obsługiwać duże liczby całkowite, takie jak resourceVersion lub znaczniki czasu w nanosekundach?
metadata.resourceVersion Kubernetes jest celowo polem ciągowym — zespół Kubernetes wiedział, że parsery JSON w JavaScript i innych środowiskach opartych na float64 traciłyby precyzję na dużych liczbach całkowitych. Zawsze traktuj je jako ciąg. Dla genuinally numerycznych dużych liczb całkowitych (jak nanosekundowe znaczniki czasu epoch w niektórych systemach śledzenia), użyj typu int Pythona, int64 Go lub BigInt Node.js do parsowania. Nigdy nie przekazuj ich przez JSON.parse() w JavaScript bez niestandardowej funkcji revivor. Przy konwersji do YAML te duże liczby całkowite są bezpieczne — YAML nie ma limitu precyzji dla liczb całkowitych. Niebezpieczeństwo leży w konwersji w obie strony przez parser JSON JavaScript.
Czy YAML 1.2 jest już powszechnie przyjęty?
Nierównomiernie. Główne biblioteki językowe migrują: yaml.v3 Go, ruamel.yaml Pythona i eemeli/yaml Node.js wszystkie obsługują lub domyślnie używają YAML 1.2. Ale Kubernetes, Ansible i większość ekosystemu DevOps nadal działa na parserach YAML 1.1 z powodu kosztu migracji w kierunku wstecznej kompatybilności. Adopcja YAML 1.2 w nowych projektach jest zalecana, ale zakładaj wersję 1.1 dla każdego systemu, którego nie skonfigurowałeś samodzielnie.
Czy nasz zespół powinien ustandaryzować JSON czy YAML dla konfiguracji?
Ustandaryzuj ze względu na cel, nie na format. Używaj JSON dla konfiguracji konsumowanych przez kod (treści żądań API, pliki konfiguracyjne SDK, narzędzia programistyczne). Używaj YAML dla konfiguracji konsumowanych przez ludzi (manifesty Kubernetes, potoki CI, konfiguracje wdrożeń, Ansible playbook). Unikaj mieszania obu formatów dla tej samej konfiguracji — wybierz jedną reprezentację dla każdego typu konfiguracji i automatyzuj konwersję jeśli potrzebujesz obu. Gdy musisz konwertować, zarówno nasz konwerter JSON na YAML jak i konwerter YAML na JSON działają w całości w przeglądarce — żadne dane nie opuszczają urządzenia.
Wypróbuj teraz
Gotowy do konwersji prawdziwego pliku? Wypróbuj nasz konwerter JSON na YAML do bezpiecznej konwersji JSON do YAML Kubernetes — automatycznie cytuje słowa Norway (NO, yes, on, off i pełną listę boolowską YAML 1.1) i pozwala wybrać wcięcie 2- lub 4-spacjowe. Dla odwrotnego kierunku nasz konwerter YAML na JSON obsługuje kotwice, aliasy i wielodokumentowy YAML. Oba narzędzia działają w całości w przeglądarce — dane nigdy nie opuszczają urządzenia, co ma znaczenie gdy pracujesz z produkcyjnymi manifestami Kubernetes lub planami Terraform zawierającymi wrażliwe konfiguracje zasobów.