Granice konwersji JSON ↔ YAML i XML
Podsumowanie: Twierdzenie, że JSON jest podzbiorem YAML, gwarantuje tylko jeden kierunek, a XML nie ma żadnego oficjalnego odwzorowania na JSON — wszystko opiera się na konwencji.

Najważniejsze: używaj JSON jako formatu pośredniego: sparsuj źródło do modelu danych, a następnie serializuj do celu; przed konwersją zdecyduj, czy komentarze, atrybuty XML i przestrzenie nazw oraz typy numeryczne lub logiczne muszą przetrwać.
Czy potok konwersji jest użyteczny, nie zależy od tego, jak ładnie wygląda wynik, ale od tego, czy zdajesz sobie sprawę, ile odrzucił.
1. JSON i YAML: jednostronne zawieranie
Specyfikacja YAML 1.2 mówi wprost: JSON jest podzbiorem YAML. To znaczy, że każdy poprawny JSON jest poprawnym YAML-em:
{"name": "json", "tags": ["a", "b"], "count": 3}
Ten fragment jest jednocześnie JSON-em i YAML-em w stylu flow. Odwrotnie nie zachodzi.
| Cecha | JSON | YAML 1.2 |
|---|---|---|
| Komentarze | ✗ | ✓ |
| Kotwice i aliasy | ✗ | ✓ |
Wiele dokumentów (---) |
✗ | ✓ |
| Klucze niebędące łańcuchami | ✗ | ✓ (1: a, true: b) |
| Klucze złożone | ✗ | ✓ (? [a, b]) |
| Wrażliwość na wcięcia | ✗ | ✓ (wcięcia tabulatorem są odrzucane) |
| Wnioskowanie typów | jawne | niejawne (0o17, 0x1F, .inf) |
| Czytelność jednoliniowa | dobra | słaba (długa treść opiera się na składni składania) |
1.1 Wnioskowanie typów to największa pułapka
YAML parsuje niektóre wartości wyglądające jak łańcuchy do innych typów:
zip: 01234 # string or octal integer? the YAML 1.2 core schema treats it as a string
version: 1.10 # 1.1 (a float); the semantics are lost
country: NO # a string in YAML 1.2; boolean false in YAML 1.1
date: 2026-10-30 # some implementations parse it as a date object
Przykład z NO sparzył wiele osób: w parserze YAML 1.1 (niektóre starsze biblioteki) country: NO staje się false.
Środek zaradczy: ujmuj w cudzysłowy każdy identyfikator, który „wygląda jak liczba lub wartość logiczna”, jak w version: "1.10".
1.2 Kotwice, aliasy i wiele dokumentów
defaults: &defaults
retries: 3
timeout: 30
serviceA:
<<: *defaults
host: a.example.com
---
serviceB:
<<: *defaults
host: b.example.com
Po rozwinięciu kotwic i konwersji na JSON zwykle otrzymujesz dwa zduplikowane obiekty konfiguracyjne albo jedną tablicę (wiele dokumentów musi stać się [{...}, {...}]).
W obu przypadkach kotwice nie są przywracane przy konwersji odwrotnej — ta strata jest nieunikniona.
1.3 Konwersja zachowująca komentarze
Większość bibliotek po prostu odrzuca komentarze podczas konwersji. Gdy musisz je zachować, użyj Document API pakietu yaml:
import { parseDocument } from 'yaml';
const document = parseDocument(`
# Service configuration
port: 8080
`);
document.set('port', 3000);
console.log(document.toString());
// # Service configuration
// port: 3000
Test jest prosty: jeśli ten plik będzie wielokrotnie edytowany przez ludzi, nie wybieraj ścieżki „parsowanie → obiekt → serializacja”.
1.4 Jak zweryfikować spójność w obie strony
/**
* Verifies round-trip consistency for JSON → YAML → JSON.
* @param sourceValue Original JSON value
* @param stringify YAML serialization function
* @param parse YAML parsing function
* @returns Whether the two results are exactly equal
*/
function isRoundTripStable(
sourceValue: unknown,
stringify: (input: unknown) => string,
parse: (input: string) => unknown,
): boolean {
const yamlText = stringify(sourceValue);
const restored = parse(yamlText);
return JSON.stringify(restored) === JSON.stringify(sourceValue);
}
Uruchomienie jej na prawdziwych danych jest warte więcej niż dziesięciokrotne czytanie dokumentacji. Upewnij się, że zestaw próbek obejmuje: null, pusty łańcuch, pustą tablicę, "00", "1e5", ujemne zero, liczby całkowite większe niż 2^53 oraz długie łańcuchy zawierające nowe linie.
2. JSON i XML: nie ma standardowego odwzorowania
XML jest znacznie starszy od JSON i ma o wiele silniejsze możliwości strukturalne (atrybuty, przestrzenie nazw, treść mieszana, DTD). Między nimi nie ma oficjalnej specyfikacji odwzorowania; każde narzędzie robi to samo: wybiera konwencję, a potem stara się wiele nie zgubić.
2.1 Typowe konwencje
| Konwencja | Przedstawienie | Przykład |
|---|---|---|
| Element → klucz obiektu | Element o tej samej nazwie staje się polem | <a>1</a> → {"a": "1"} |
Atrybut → prefiks @ |
@name |
<a id="1"/> → {"a": {"@id": "1"}} |
Tekst → #text |
Gdy występuje zarówno atrybut, jak i tekst | <a id="1">hi</a> → {"a": {"@id": "1", "#text": "hi"}} |
| Powtarzający się element → tablica | Dwa lub więcej wystąpień staje się tablicą | <i>1</i><i>2</i> → {"i": ["1","2"]} |
| Przestrzeń nazw → prefiks | ns:tag |
<ns:tag/> → {"ns:tag": ...} |
<order id="A1001">
<item sku="X1" qty="2">笔记本</item>
<item sku="X2" qty="1">钢笔</item>
</order>
{
"order": {
"@id": "A1001",
"item": [
{ "@sku": "X1", "@qty": "2", "#text": "笔记本" },
{ "@sku": "X2", "@qty": "1", "#text": "钢笔" }
]
}
}
2.2 Pięć nieuniknionych strat
- Kolejność elementów: obiekty JSON są nieuporządkowane, podczas gdy XML to uporządkowana sekwencja elementów;
- Atrybuty a elementy podrzędne: w XML niosą inną semantykę, ale w JSON-ie oba stają się zwykłymi polami;
- Treść mieszana: struktury takie jak
<p>你好 <b>世界</b> 再见</p>nie da się wyrazić zwykłym obiektem; - Deklaracje przestrzeni nazw:
xmlnsto pojęcie zasięgu; po spłaszczeniu do pól zasięg znika; - CDATA i encje:
<![CDATA[...]]>i&w JSON-ie to tylko tekst.
2.3 Dylemat „jeden element czy tablica”
<list><item>1</item></list>
Przy jednym item xml2js domyślnie zwraca obiekt i zwraca tablicę dopiero, gdy są dwa.
W efekcie kod frontendu musi pisać Array.isArray(x) ? x : [x].
Środek zaradczy: włącz opcję „zawsze tablica” podczas parsowania albo zadeklaruj pole jako typ tablicowy według schematu.
import { parseStringPromise } from 'xml2js';
const parsed = await parseStringPromise(xmlText, {
explicitArray: true, // return an array even for a single element
trim: true,
explicitRoot: false,
});
2.4 Przypomnienie o bezpieczeństwie
Przed parsowaniem niezaufanego XML musisz wyłączyć encje zewnętrzne, inaczej możesz wywołać XXE (odczyt plików lokalnych, sondowanie sieci wewnętrznej):
// In the Node ecosystem, DTDs and external entities are usually disabled through parser options
const parserOptions = { doctype: false, externalEntities: false };
XML → JSON to tylko pierwsza warstwa konwersji; konwersja nie eliminuje powierzchni ataku, którą wnosi XML.
3. Gdzie pasuje każdy z trzech formatów
| Scenariusz | Zalecany format | Powód |
|---|---|---|
| Publiczne API HTTP | JSON | Natywnie wspierany przez przeglądarki i każdy język |
| Konfiguracja utrzymywana ręcznie | YAML | Komentarze, wcięcia, czytelność |
| Dokumenty wymagające silnego schematu i przestrzeni nazw (SOAP, Office Open XML, SVG) | XML | Możliwości strukturalne i istniejący ekosystem |
| Współpraca z narzędziami operacyjnymi (K8s, CI) | YAML | Domyślny wybór ekosystemu |
| Przechowywanie trwałe, cache, logi | JSON / JSONL | Uniwersalne i niezależne od zależności |
4. Uwagi implementacyjne
import { stringify as toYaml } from 'yaml';
/**
* Converts a JSON value to YAML text while keeping Chinese characters unescaped.
* @param value The JSON value to convert
* @returns The YAML text
*/
function jsonToYaml(value: unknown): string {
return toYaml(value, {
indent: 2,
lineWidth: 0, // disable automatic wrapping so long strings are not split
defaultStringType: 'QUOTE_DOUBLE',
});
}
Kilka praktycznych opcji:
lineWidth: 0wyłącza automatyczne zawijanie; inaczej długie URL-e są dzielone na wiersze i stają się trudne do czytania;- ujęcie wszystkiego w podwójne cudzysłowy zapobiega błędnemu odczytaniu wartości takich jak
NOi1.10; - przy danych o wrażliwej precyzji numerycznej przekonwertuj je na łańcuchy przed serializacją.
W kierunku XML ustal jedną funkcję mapowania i umieść „prefiks atrybutu”, „nazwę klucza tekstowego” i „strategię tablic” w jej sygnaturze, zamiast rozrzucać je po kodzie:
type XmlMappingOptions = {
attributePrefix: string;
textKey: string;
alwaysArray: boolean;
};
const defaultMapping: XmlMappingOptions = {
attributePrefix: '@',
textKey: '#text',
alwaysArray: false,
};
5. Lista kontrolna
- [ ] Każdy identyfikator „podobny do liczby/wartości logicznej” w YAML jest ujęty w cudzysłowy;
- [ ] Zestaw próbek walidacyjnych dla JSON → YAML → JSON jest zdefiniowany i pokryty testami;
- [ ] Gdy komentarze muszą zostać zachowane, użyto Document API zamiast obiegu przez obiekt;
- [ ] Konwencja odwzorowania XML jest spisana, a zespół dzieli jeden zestaw parametrów;
- [ ] DTD i encje zewnętrzne są wyłączone podczas parsowania niezaufanego XML;
- [ ] Liczba bajtów i liczba pól są porównywane przed i po konwersji, a każda anomalia wywołuje alert.
FAQ
Jak wybrać kierunek konwersji między JSON, YAML i XML?
Preferuj JSON jako format pośredni: najpierw sparsuj YAML i XML do modelu danych, a następnie serializuj z tego modelu, zamiast przepisywać tekst. Użyj Document API YAML, gdy komentarze muszą przetrwać, i ustal jedną konwencję odwzorowania XML, zanim pojawi się potrzeba atrybutów lub przestrzeni nazw.
Dlaczego konwersja XML do JSON gubi atrybuty i przestrzenie nazw?
JSON nie ma pojęcia atrybutów ani przestrzeni nazw, więc atrybuty XML, prefiksy i URI przestrzeni nazw trzeba złożyć do zwykłych kluczy, zwykle z prefiksem @ lub węzłem #text. Gdy reguły mapowania się rozjeżdżają, ten sam XML daje różny JSON i nie da się go odtworzyć.
Czy utrzymywać JSON, YAML i XML w długoterminowej dwukierunkowej synchronizacji?
Nie. Komentarze, kotwice, atrybuty XML i przestrzenie nazw są stratne między formatami, a dwukierunkowa synchronizacja stale gromadzi różnice. Wybierz jeden format źródłowy, a pozostałe traktuj jako jednorazowe eksporty lub tymczasowe widoki.