Textbasierte Datenformate: JSON, XML und YAML

In diesem Notebook werden textbasierte Datenformate behandelt. Mit ihnen lassen sich strukturierte Daten speichern und zwischen Programmen, Systemen und Programmiersprachen austauschen.

Zunächst werden die wichtigsten Formate JSON, XML und YAML im Überblick vorgestellt und verglichen. Anschließend liegt der Schwerpunkt auf YAML – mit allen wichtigen Details.

Lernziele. Nach dem Durcharbeiten dieses Notebooks sollte Folgendes gelingen:

  • textbasierte Serialisierungsformate erklären und ihren Zweck benennen;
  • JSON, XML und YAML lesen, unterscheiden und ihre Einsatzgebiete zuordnen;
  • YAML im Detail anwenden (Einrückung, Skalare, Collections, mehrzeilige Strings, Anchors/Aliases, mehrere Dokumente).

Hinweis zu den Code-Beispielen. Zur Veranschaulichung werden die Formate mit kurzen Python-Programmen eingelesen und ausgegeben. Die dafür nötigen Python-Grundlagen sind im Abschnitt Das nötige Python für dieses Notebook zusammengefasst und werden an den Beispielen erklärt. Python-Vorkenntnisse sind nicht erforderlich.

Einführung: Textbasierte Datenformate

Serialisierung und Deserialisierung

Programme halten Daten während der Ausführung im Arbeitsspeicher als Datenstrukturen vor (etwa als Listen oder als Sammlungen von Schlüssel-Wert-Paaren). Sollen diese Daten gespeichert oder über ein Netzwerk verschickt werden, müssen sie in eine lineare Form gebracht werden – also in eine Folge von Zeichen bzw. Bytes.

  • Serialisierung (serialization, marshalling): die Umwandlung einer Datenstruktur aus dem Arbeitsspeicher in eine speicher- bzw. übertragbare Darstellung (zum Beispiel in einen Text).
  • Deserialisierung (deserialization, parsing): der umgekehrte Weg – aus dem Text wird wieder eine Datenstruktur im Arbeitsspeicher aufgebaut.

Eine solche Datenstruktur ist in Python beispielsweise ein Dictionary (Typname dict) – eine Sammlung von Schlüssel-Wert-Paaren, vergleichbar mit einem Wörterbuch (Stichwort → Eintrag). Was ein dict genau ist, wird weiter unten im Abschnitt Das nötige Python für dieses Notebook erklärt.

Textbasierte Formate verwenden für die serialisierte Darstellung menschenlesbaren Text (in der Regel UTF-8-kodiert) und nicht ein kompaktes, nur maschinenlesbares Binärformat.

Wozu werden solche Formate benötigt?

  • Konfiguration: Einstellungen von Programmen und Diensten (zum Beispiel docker-compose.yml, Konfigurationen von Werkzeugen oder Anwendungen).
  • Datenaustausch zwischen Systemen: Web-Schnittstellen (APIs) liefern Daten überwiegend als JSON.
  • Sprachunabhängigkeit: Ein in Python geschriebenes Programm kann Daten ablegen, die ein Java- oder JavaScript-Programm wieder einliest. Das Format ist der gemeinsame Nenner.
  • Persistenz: dauerhaftes Ablegen von Daten in Dateien.

Text- gegenüber Binärformaten

Aspekt Textformate (JSON, XML, YAML, …) Binärformate (Protobuf, MessagePack, …)
Lesbarkeit für Menschen lesbar und bearbeitbar nicht direkt lesbar
Versionsverwaltung (Diff) gut vergleichbar schlecht vergleichbar
Größe größer kompakter
Verarbeitungsgeschwindigkeit langsamer beim Einlesen schneller
Werkzeuge jeder Texteditor genügt spezielle Werkzeuge nötig

Textformate sind die erste Wahl, wenn Menschen die Daten lesen, schreiben oder versionieren – genau das trifft auf Konfigurationsdateien zu.

Gemeinsame Grundkonzepte

Obwohl sich die Syntax der Formate stark unterscheidet, beruhen JSON, XML und YAML auf denselben wenigen Bausteinen:

  • Skalare – einzelne, nicht weiter zerlegbare Werte. Beispiele: die Zeichenkette "Berlin", die Zahl 42, ein Wahrheitswert (true/false, also wahr/falsch) oder null für „kein Wert".
  • Schlüssel-Wert-Paare (auch Mapping, Objekt oder Dictionary genannt) – einem Schlüssel (Namen) wird ein Wert zugeordnet, etwa stadt → "Berlin". Das Prinzip entspricht einem Wörterbuch: Über das Stichwort wird der zugehörige Eintrag gefunden.
  • Listen (auch Sequenz oder Array) – mehrere Werte in fester Reihenfolge, zum Beispiel [8080, 8443].
  • Verschachtelung – ein Wert kann selbst wieder ein Mapping oder eine Liste sein. Dadurch entsteht eine baumartige Struktur:
datenbank
├── host  → "db.example.com"     (Skalar)
└── ports → [8080, 8443]         (Liste von Skalaren)

Sind diese Bausteine einmal vertraut, lässt sich jedes der drei Formate rasch lesen.

Querschnittsthemen

  • Zeichenkodierung: Textformate werden praktisch immer als UTF-8 (Unicode) gespeichert. Dadurch sind Umlaute, Sonderzeichen oder Emojis problemlos darstellbar. Zeichenkodierungen werden ausführlich an anderer Stelle des Moduls behandelt: http://christianherta.de/lehre/praktischeInformatik/bash/bash_3.html.
  • Schema und Validierung: Ein Schema ist eine formale Beschreibung des erlaubten Aufbaus einer Datei – also welche Felder vorkommen dürfen oder müssen, welchen Typ sie haben und wie sie verschachtelt sein dürfen. Ein Validierungswerkzeug prüft anschließend eine konkrete Datei gegen dieses Schema und meldet Abweichungen. Das ist vergleichbar mit einer Rechtschreibprüfung, jedoch bezogen auf die Struktur der Daten statt auf Text. Bekannte Schema-Sprachen sind JSON Schema (für JSON) sowie XML Schema/XSD und DTD (für XML).
  • Kommentare: Nicht jedes Format erlaubt Kommentare – ein wichtiger Unterschied, der weiter unten betrachtet wird.

Das nötige Python für dieses Notebook

Die Code-Beispiele verwenden nur wenige Python-Konzepte. Sie werden hier kurz erklärt; mehr Python ist zum Verständnis nicht erforderlich. Eine Code-Zelle wird ausgeführt, indem sie ausgewählt und Shift+Enter gedrückt wird; das Ergebnis erscheint darunter.

  • Modul importieren. Mit import json wird eine fertige Sammlung von Funktionen (ein Modul) geladen. Eine darin enthaltene Funktion wird über den Modulnamen angesprochen, etwa json.loads(...) – die Funktion loads aus dem Modul json.

  • Funktionsaufruf und Rückgabewert. ergebnis = json.loads(text) ruft die Funktion mit der Eingabe text auf. Das berechnete Ergebnis wird der Variablen ergebnis zugewiesen und steht danach zur Verfügung.

  • Ausgabe. print(x) gibt den Wert x aus.

  • Zeichenkette (str). Text steht in Anführungszeichen: "hallo". Drei doppelte Anführungszeichen am Anfang und am Ende (""") erlauben mehrzeiligen Text. Das wird genutzt, um JSON-/YAML-Beispiele direkt im Code abzulegen.

  • Liste (list). Eine geordnete Sammlung in eckigen Klammern: [8080, 8443]. Der Zugriff erfolgt über die Position (den Index, beginnend bei 0): liste[0] ergibt das erste Element.

  • Dictionary (dict). Eine Sammlung von Schlüssel-Wert-Paaren in geschweiften Klammern: {"host": "db.example.com", "port": 5432}. Der Zugriff erfolgt über den Schlüssel: d["host"]. Ein dict ist das Python-Gegenstück zu den Mappings der Datenformate.

  • for-Schleife. for element in liste: durchläuft eine Liste und weist der Variablen element nacheinander jeden Eintrag zu; der eingerückte Block darunter wird je Eintrag einmal ausgeführt. Ein dict lässt sich ebenso durchlaufen – am bequemsten mit der Methode d.items(). Sie liefert pro Durchlauf ein Paar aus Schlüssel und Wert, das direkt zwei Schleifenvariablen zugewiesen wird: for schluessel, wert in d.items():. Beispiel:

    d = {"host": "db.example.com", "port": 5432}
    for schluessel, wert in d.items():
        print(schluessel, "=>", wert)
    # Ausgabe:
    # host => db.example.com
    # port => 5432
    
  • type(x) nennt den Datentyp eines Werts (etwa str oder int); repr(x) liefert eine eindeutige Textdarstellung (bei Zeichenketten zum Beispiel mit Anführungszeichen).

Diese Bausteine genügen, um alle Beispiele zu verstehen.

Überblick und Vergleich der Formate

JSON – JavaScript Object Notation

Aus JavaScript hervorgegangen und heute das Standardformat für Web-Schnittstellen (APIs). JSON ist kompakt, einfach und wird von praktisch jeder Programmiersprache unterstützt.

XML – eXtensible Markup Language

Ein älteres, sehr mächtiges Format aus der Dokumentenwelt. Es verwendet Tags zur Auszeichnung – das sind Markierungen in spitzen Klammern, etwa <name>…</name>, ähnlich wie in HTML (eine genauere Erklärung folgt im XML-Abschnitt). XML ist im Unternehmensumfeld verbreitet und überall dort, wo Text und Struktur gemischt vorkommen (zum Beispiel in Office-Dokumenten, in SVG-Grafiken oder in RSS-Nachrichtenfeeds).

YAML – YAML Ain't Markup Language

Auf Lesbarkeit für Menschen optimiert. YAML verzichtet weitgehend auf Klammern und nutzt stattdessen Einrückung. Es ist für Konfigurationsdateien sehr verbreitet (Docker Compose, Kubernetes, CI/CD-Pipelines, Ansible).

Weitere Formate (zur Einordnung)

  • TOML: ein gut lesbares Konfigurationsformat, das verwandte Einstellungen in benannten Abschnitten (in eckigen Klammern, etwa [server]) gruppiert; in der Python-Welt zur Projektkonfiguration verbreitet.
  • INI: ein sehr einfaches Format aus Schlüssel-Wert-Zeilen, gegliedert in Abschnitte.
  • CSV: tabellarische Daten, eine Zeile pro Datensatz, Felder durch Kommata getrennt.
  • Protobuf / MessagePack: binäre Gegenbeispiele für den effizienten Austausch großer Datenmengen zwischen Maschinen.

Vergleich auf einen Blick

Kriterium JSON XML YAML
Lesbarkeit mittel gering hoch
Kommentare ❌ nein ✅ ja <!-- --> ✅ ja #
Datentypen Zeichenkette, Zahl, Wahrheitswert, null, Liste, Objekt grundsätzlich Text (Typen über Schema) Zeichenkette, Ganzzahl, Gleitkommazahl, Wahrheitswert, null, Listen, Mappings, Datum
Syntax {} [] , : <tag>…</tag> Einrückung
Schema/Validierung JSON Schema DTD, XSD extern (z. B. JSON Schema)
Mehrzeilige Zeichenketten umständlich (\n) möglich sehr komfortabel
Referenzen/Wiederverwendung ❌ nein begrenzt ✅ Anchors/Aliases
Typischer Einsatz Web-APIs Dokumente, Unternehmensumfeld Konfiguration, DevOps

Eine wichtige Beziehung: JSON ist eine Teilmenge von YAML – jedes gültige JSON-Dokument ist zugleich gültiges YAML. Darauf wird später eingegangen.

JSON

Syntax

JSON kennt genau zwei Strukturtypen und vier Skalartypen:

  • Objekt { }: eine ungeordnete Menge von Paaren der Form "schlüssel": wert (Schlüssel sind stets Zeichenketten in "…").
  • Array [ ]: eine geordnete Liste von Werten.
  • Skalare: Zeichenkette ("text"), Zahl (42, 3.14), Wahrheitswert (true / false), null (kein Wert).

Werte werden durch Kommata getrennt. Wichtig: kein Komma nach dem letzten Element (kein trailing comma), und keine Kommentare.

Beispiel – die Konfiguration eines (fiktiven) Webservices:

{
  "name": "webservice",
  "version": 2,
  "enabled": true,
  "replicas": 3,
  "ports": [8080, 8443],
  "database": {
    "host": "db.example.com",
    "port": 5432
  },
  "tags": null
}

Erläuterung des Beispiels. Das äußere { … } ist ein Objekt mit mehreren Feldern. name ist eine Zeichenkette; version und replicas sind Zahlen; enabled ist ein Wahrheitswert. ports ist ein Array (eine Liste) mit zwei Zahlen. Das Feld database ist selbst wieder ein Objekt – ein Beispiel für Verschachtelung – mit den Feldern host und port. Das Feld tags hat den Wert null, ist also bewusst leer gelassen.

JSON mit Python verarbeiten

Python enthält ein fertiges Modul namens json, das zwischen JSON-Text und Python-Datenstrukturen umwandelt:

  • json.loads(text) → Python-Datenstruktur (loads = load from string) — entspricht der Deserialisierung.
  • json.dumps(obj) → JSON-Text (dumps = dump to string) — entspricht der Serialisierung.

Die Typen entsprechen einander wie folgt: JSON-Objekt ↔ dict, JSON-Array ↔ list, true/falseTrue/False, nullNone.

In [1]:
import json   # Modul aus der Python-Standardbibliothek laden

# Dreifache Anfuehrungszeichen erlauben mehrzeiligen Text:
json_text = """
{
  "name": "webservice",
  "version": 2,
  "enabled": true,
  "ports": [8080, 8443],
  "database": {"host": "db.example.com", "port": 5432},
  "tags": null
}
"""

# Deserialisierung: JSON-Text -> Python-Datenstruktur (hier ein dict)
data = json.loads(json_text)

print(type(data))                  # <class 'dict'>
print(data["database"]["port"])    # Zugriff ueber Schluessel: 5432
print(data["enabled"], data["tags"])  # True None  (aus true / null)
<class 'dict'>
5432
True None
In [2]:
# Serialisierung: Python-Datenstruktur -> JSON-Text
obj = {
    "name": "webservice",
    "ports": [8080, 8443],   # eine Liste
    "enabled": True,
    "tags": None,
}
# indent=2 sorgt fuer eine eingerueckte, gut lesbare Ausgabe
print(json.dumps(obj, indent=2))
{
  "name": "webservice",
  "ports": [
    8080,
    8443
  ],
  "enabled": true,
  "tags": null
}

Grenzen von JSON

  • Keine Kommentare – ungünstig für Konfigurationsdateien, die erklärt werden sollen.
  • Keine Referenzen/Wiederverwendung – wiederkehrende Blöcke müssen kopiert werden.
  • Eingeschränkte Typen – es gibt zum Beispiel kein eingebautes Format für Datum oder Uhrzeit.
  • Strikte Syntax – ein vergessenes oder ein überzähliges Komma macht die Datei ungültig.

JSON Schema (kurz)

Mit JSON Schema wird – selbst wieder in JSON formuliert – beschrieben, welche Felder vorhanden sein müssen und welche Typen sie haben. Werkzeuge können eine Datei dann automatisch dagegen prüfen (validieren). Dies wird hier nur zur Einordnung erwähnt.

XML

Syntax

XML beschreibt Daten mit Elementen, die durch Tags begrenzt werden. Ein Tag ist eine Markierung in spitzen Klammern. Zu jedem Element gehören ein Start-Tag <name> und ein End-Tag </name>. Ein Element kann

  • Text enthalten,
  • weitere Elemente verschachteln,
  • Attribute im Start-Tag tragen: <server port="8080"> (das Attribut port hat den Wert 8080).

Es gibt genau ein Wurzelelement (root), das alle übrigen umschließt.

Dasselbe Beispiel wie zuvor, nun in XML:

<?xml version="1.0" encoding="UTF-8"?>
<webservice version="2" enabled="true">
  <ports>
    <port>8080</port>
    <port>8443</port>
  </ports>
  <database host="db.example.com" port="5432"/>
  <!-- Kommentare sind in XML erlaubt -->
</webservice>

Auffällig ist, dass XML deutlich ausführlicher als JSON oder YAML ist, weil jeder Tag-Name doppelt erscheint (im Start- und im End-Tag). Außerdem besteht für denselben Wert oft die Wahl zwischen einem Attribut und einem Kind-Element.

XML mit Python einlesen

Die Python-Standardbibliothek stellt dafür das Modul xml.etree.ElementTree bereit (hier mit ET abgekürzt). Es liest XML-Text ein und stellt ihn als Baum von Elementen dar, durch den navigiert werden kann.

In [3]:
import xml.etree.ElementTree as ET   # Modul laden und kurz "ET" nennen

xml_text = """
<webservice version="2" enabled="true">
  <ports>
    <port>8080</port>
    <port>8443</port>
  </ports>
  <database host="db.example.com" port="5432"/>
</webservice>
"""

root = ET.fromstring(xml_text)   # XML-Text einlesen -> Wurzelelement
print(root.tag)                  # Name des Tags: webservice
print(root.attrib)               # Attribute als dict: {'version': '2', 'enabled': 'true'}

# Alle <port>-Elemente durchlaufen und ihren Textinhalt einsammeln.
# Hinweis: Text aus XML ist immer eine Zeichenkette ('8080', nicht 8080).
ports = []
for p in root.findall("./ports/port"):
    ports.append(p.text)         # .text ist der Inhalt zwischen <port> und </port>
print(ports)                     # ['8080', '8443']

db = root.find("database")
print(db.attrib["host"], db.attrib["port"])   # db.example.com 5432
webservice
{'version': '2', 'enabled': 'true'}
['8080', '8443']
db.example.com 5432

Das XML-Ökosystem (nur erwähnt)

Rund um XML existiert eine ganze Familie verwandter Standards:

  • DTD / XSD: Schema-Sprachen zur Validierung.
  • XPath: eine Abfragesprache zum Navigieren im Baum.
  • XSLT: eine Sprache zur Transformation von XML in andere Formate.
  • Namespaces (Namensräume): vermeiden Namenskonflikte (xmlns:…).

Wann XML, wann JSON?

  • XML: bei gemischten Inhalten aus Text und Auszeichnung (Dokumenten), bei etablierten Standards im Unternehmensumfeld sowie dann, wenn Validierung und Transformation zentral sind.
  • JSON: beim schlanken Datenaustausch, für Web-APIs und einfache Strukturen.

YAML – der Schwerpunkt

Was ist YAML?

YAML steht (selbstbezüglich-ironisch) für „YAML Ain't Markup Language". Das Entwurfsziel ist eine möglichst gute Lesbarkeit für Menschen. YAML verzichtet weitgehend auf Klammern und Anführungszeichen und nutzt stattdessen Einrückung zur Strukturierung – ähnlich wie es Python beim Quelltext tut.

Zwei zentrale Punkte vorab:

  1. YAML ist ein Superset von JSON – jedes JSON-Dokument ist zugleich gültiges YAML.
  2. Gerade die Bequemlichkeit (etwa das automatische Erraten des Typs) führt zu einigen bekannten Stolperfallen. Diese werden gezielt betrachtet.

In den folgenden Beispielen wird die Python-Bibliothek PyYAML verwendet (Import mit import yaml). Falls sie nicht installiert ist, kann sie auf der Kommandozeile mit pip install pyyaml nachinstalliert werden. Die zentrale Funktion ist yaml.safe_load(text), die einen YAML-Text einliest und – wie json.loads – eine Python-Datenstruktur (meist ein dict oder eine list) zurückgibt.

In [4]:
# Falls noetig zuvor in der Shell installieren:  pip install pyyaml
import yaml
print("PyYAML-Version:", yaml.__version__)
PyYAML-Version: 6.0.2

Grundstruktur und Einrückung

Die Einrückung legt fest, was zu was gehört (die Verschachtelung). Es gelten folgende Regeln:

  • Eingerückt wird mit Leerzeichenniemals mit Tabulatoren! (Tabs sind in YAML zur Einrückung unzulässig.)
  • Üblich sind zwei Leerzeichen pro Ebene; wichtig ist vor allem Konsistenz.
  • Elemente auf gleicher Einrückungstiefe gehören zur selben Struktur.
name: webservice
database:
  host: db.example.com   # zwei Leerzeichen eingerueckt -> gehoert zu 'database'
  port: 5432

Das entspricht dem JSON {"name": "webservice", "database": {"host": "db.example.com", "port": 5432}}.

Skalare und die automatische Typerkennung

YAML errät den Typ eines nicht in Anführungszeichen gesetzten Skalars:

eine_zeichenkette: hallo welt     # Zeichenkette (ganz ohne Anfuehrungszeichen!)
eine_ganzzahl:     42             # Ganzzahl (int)
eine_kommazahl:    3.14           # Gleitkommazahl (float)
ein_wahrheitswert: true           # Wahrheitswert (bool)
nichts:            null           # kein Wert (in Python: None; auch ~ oder leer)
ein_datum:         2026-05-29     # Datum

Zeichenketten benötigen in der Regel keine Anführungszeichen. Setzen lassen sie sich dennoch:

  • 'einfach' (einfache Anführungszeichen): nahezu keine Sonderzeichen-Auflösung.
  • "doppelt" (doppelte Anführungszeichen): Escape-Sequenzen wie \n (Zeilenumbruch) oder \t (Tabulator) werden interpretiert.
In [5]:
import yaml

doc = """
eine_zeichenkette: hallo welt
eine_ganzzahl:     42
eine_kommazahl:    3.14
ein_wahrheitswert: true
nichts:            null
ein_datum:         2026-05-29
"""
data = yaml.safe_load(doc)          # YAML-Text -> Python-dict

# data.items() liefert die (Schluessel, Wert)-Paare; type(v) nennt den Typ:
for schluessel, wert in data.items():
    print(schluessel, "->", repr(wert), "  Typ:", type(wert).__name__)
eine_zeichenkette -> 'hallo welt'   Typ: str
eine_ganzzahl -> 42   Typ: int
eine_kommazahl -> 3.14   Typ: float
ein_wahrheitswert -> True   Typ: bool
nichts -> None   Typ: NoneType
ein_datum -> datetime.date(2026, 5, 29)   Typ: date

⚠️ Bekannte Stolperfallen der Typerkennung

1. Wahrheitswerte (das „Norway-Problem"). YAML (in der weit verbreiteten Version 1.1) deutet zahlreiche Wörter als Wahrheitswert: yes/no, on/off, true/false. Dadurch wird auch das Länderkürzel NO (Norwegen) zu False!

laender:
  - NO     # wird zu False statt zur Zeichenkette "NO"!
  - SE

2. Führende Null und Versionsnummern. version: 1.10 wird zur Gleitkommazahl 1.1. Ein Wert mit führender Null wird als Oktalzahl gelesen: 01067 ergibt 567! (Das gilt nur, wenn alle Ziffern zwischen 0 und 7 liegen; 08000 bleibt zufällig eine Zeichenkette – also inkonsistent und unzuverlässig.) Betroffen sind etwa Postleitzahlen oder Telefonnummern.

Abhilfe: im Zweifel in Anführungszeichen setzen – dann ist der Wert garantiert eine Zeichenkette:

laender:  ["NO", "SE"]
version:  "1.10"
plz:      "01067"   # sonst Oktalzahl 567
In [6]:
import yaml

tricky = """
ohne_anfuehrungszeichen:
  - NO
  - yes
  - on
  - 1.10
  - 01067
mit_anfuehrungszeichen:
  - "NO"
  - "1.10"
  - "01067"
"""
print(yaml.safe_load(tricky))
# NO/yes/on werden zu Wahrheitswerten, 1.10 zur Kommazahl, 01067 zur Oktalzahl 567.
{'ohne_anfuehrungszeichen': [False, True, True, 1.1, 567], 'mit_anfuehrungszeichen': ['NO', '1.10', '01067']}

Collections: Mappings und Sequenzen

Mapping (Schlüssel-Wert-Paare; entspricht einem Python-dict):

database:
  host: db.example.com
  port: 5432

Begriffsklärung „Mapping". Der Begriff Mapping (deutsch Abbildung) bezeichnet die gesamte Zuordnung über alle Schlüssel – nicht ein einzelnes Schlüssel-Wert-Paar. Das obige Mapping bildet zwei Schlüssel ab (host und port) und besteht daher aus zwei Einträgen (Paaren), ist aber ein Mapping. Ein einzelnes Paar wie host: db.example.com ist nur ein Eintrag dieser Abbildung. (Analogie aus der Mathematik: Die Funktion $f(x)=x^2$ ist eine Abbildung, obwohl ihr „Graph" aus vielen Paaren besteht – $(1,1), (2,4), (3,9), \dots$. Die Abbildung ist die ganze Zuordnungsvorschrift, nicht ein einzelnes Paar.)

Sequenz (Liste; entspricht einer Python-list) – jedes Element beginnt mit - (Bindestrich und Leerzeichen):

ports:
  - 8080
  - 8443

Zusätzlich gibt es den Flow-Stil (JSON-ähnlich, in einer Zeile):

ports: [8080, 8443]
database: {host: db.example.com, port: 5432}

Der Block-Stil (eingerückt, mehrzeilig) dient der Lesbarkeit; der Flow-Stil eignet sich für kurze, kompakte Werte.

In [7]:
import yaml

doc = """
# Block-Stil
ports:
  - 8080
  - 8443
database:
  host: db.example.com
  port: 5432
# Flow-Stil (gleichbedeutend)
ports_flow: [8080, 8443]
db_flow: {host: db.example.com, port: 5432}
"""
print(yaml.safe_load(doc))
{'ports': [8080, 8443], 'database': {'host': 'db.example.com', 'port': 5432}, 'ports_flow': [8080, 8443], 'db_flow': {'host': 'db.example.com', 'port': 5432}}

Verschachtelung komplexer Strukturen

Mappings und Sequenzen lassen sich beliebig kombinieren: Der Wert eines Schlüssels darf selbst wieder ein Mapping oder eine Sequenz sein. So entsteht die baumartige Struktur.

Mapping innerhalb eines Mappings ("Mapping von Mappings"). Im Beispiel

database:
  host: db.example.com
  port: 5432

ist database ein Schlüssel, dessen Wert selbst wieder ein Mapping ist – nämlich das eine Mapping {host: db.example.com, port: 5432}. Wichtig: Das ist ein Mapping mit zwei Einträgen (zwei Schlüssel-Wert-Paaren), nicht etwa zwei Mappings. Die Zeile host: db.example.com für sich ist kein eigenes Mapping, sondern nur ein Eintrag dieses Mappings. Die Gesamtstruktur ist also ein Mapping, dessen Wert erneut ein Mapping ist (dessen Werte hier Skalare sind). In Python entspricht das einem dict, das ein weiteres dict mit zwei Einträgen enthält: {"database": {"host": "db.example.com", "port": 5432}}.

Liste von Mappings. Ebenso häufig ist eine Liste, deren Elemente Mappings sind – etwa mehrere Benutzer mit jeweils mehreren Feldern:

users:
  - name: alice
    role: admin
  - name: bob
    role: user

Auf die Einrückung ist zu achten: Das - kennzeichnet ein Listenelement; name und role desselben Elements stehen auf gleicher Tiefe.

In [8]:
import yaml

doc = """
users:
  - name: alice
    role: admin
    active: true
  - name: bob
    role: user
    active: false
"""
data = yaml.safe_load(doc)
print(type(data["users"]).__name__, "mit", len(data["users"]), "Eintraegen")
print(data["users"][0])    # erstes Element: {'name': 'alice', 'role': 'admin', 'active': True}
list mit 2 Eintraegen
{'name': 'alice', 'role': 'admin', 'active': True}

Mehrzeilige Zeichenketten: Block-Skalare

Für längere Texte (etwa Skripte oder Befehle) gibt es zwei Block-Stile:

  • Literal-Stil |: Zeilenumbrüche bleiben erhalten.
  • Folded-Stil >: Zeilenumbrüche werden zu Leerzeichen zusammengefaltet (eine Leerzeile erzeugt dennoch einen echten Umbruch).
literal: |
  Zeile 1
  Zeile 2
folded: >
  Diese Zeilen werden
  zu einer langen Zeile
  zusammengefuegt.

Chomping-Indikatoren steuern den abschließenden Zeilenumbruch:

  • | / > : clip – genau ein abschließender Umbruch (Standardverhalten).
  • |- / >- : strip – kein abschließender Umbruch.
  • |+ / >+ : keep – alle abschließenden Leerzeilen bleiben erhalten.
In [9]:
import yaml

doc = """
literal: |
  Zeile 1
  Zeile 2
literal_strip: |-
  ohne abschliessenden Umbruch
folded: >
  Diese Zeilen werden
  zu einer langen Zeile
  zusammengefuegt.
"""
data = yaml.safe_load(doc)
for schluessel, wert in data.items():
    print("---", schluessel, "---")
    print(repr(wert))     # repr macht die Zeilenumbrueche (\n) sichtbar
--- literal ---
'Zeile 1\nZeile 2\n'
--- literal_strip ---
'ohne abschliessenden Umbruch'
--- folded ---
'Diese Zeilen werden zu einer langen Zeile zusammengefuegt.\n'

Kommentare

Alles ab einem # bis zum Zeilenende ist ein Kommentar und wird beim Einlesen ignoriert. Das ist einer der größten Vorteile gegenüber JSON für Konfigurationsdateien.

# Konfiguration des Webservices
port: 8080   # Standard-HTTP-Port (intern)

Anchors, Aliases und Merge Keys (Wiederverwendung)

YAML kann Teile eines Dokuments benennen und wiederverwenden; das vermeidet Duplikate:

  • Anchor &name: markiert einen Knoten mit einem Namen.
  • Alias *name: verweist auf den markierten Knoten und fügt ihn ein.
  • Merge Key <<: fügt die Schlüssel eines so markierten Mappings ein (und erlaubt, einzelne Werte zu überschreiben).
defaults: &defaults       # Anchor mit dem Namen 'defaults'
  restart: always
  logging: json

service_a:
  <<: *defaults           # uebernimmt restart und logging
  image: nginx

service_b:
  <<: *defaults
  image: redis
  restart: on-failure     # ueberschreibt den geerbten Wert
In [10]:
import yaml

doc = """
defaults: &defaults
  restart: always
  logging: json

service_a:
  <<: *defaults
  image: nginx

service_b:
  <<: *defaults
  image: redis
  restart: on-failure
"""
data = yaml.safe_load(doc)
print("service_a:", data["service_a"])
print("service_b:", data["service_b"])    # restart wurde ueberschrieben
service_a: {'restart': 'always', 'logging': 'json', 'image': 'nginx'}
service_b: {'restart': 'on-failure', 'logging': 'json', 'image': 'redis'}

Tags und explizite Typen (kurz)

Mit Tags lässt sich der Typ erzwingen, falls die automatische Erkennung nicht ausreicht:

als_zeichenkette: !!str 42      # ergibt "42" statt der Zahl 42
als_ganzzahl:     !!int "42"    # ergibt 42 statt der Zeichenkette "42"

Es gibt auch anwendungsspezifische Tags. Im Alltag werden Tags selten benötigt – meist genügt das Setzen von Anführungszeichen. Sicherheitshinweis: Manche Tags können beim Einlesen Objekte erzeugen. Deshalb sollte stets safe_load verwendet werden (siehe Abschnitt YAML mit Python: PyYAML im Detail).

Mehrere Dokumente in einer Datei

Eine YAML-Datei kann mehrere Dokumente enthalten, getrennt durch ---. Optional markiert ... das Ende eines Dokuments. Dies wird zum Beispiel von Kubernetes intensiv genutzt (mehrere Ressourcen in einer Datei).

---
name: dokument_1
---
name: dokument_2
...

Warum mehrere Dokumente? Manchmal gehören mehrere voneinander unabhängige Konfigurationen logisch zusammen und sollen in einer Datei abgelegt werden – etwa bei Kubernetes mehrere Ressourcen (ein Service, ein Deployment usw.). Statt vieler Einzeldateien genügt eine Datei, deren Dokumente durch --- getrennt sind.

Einlesen – safe_load gegenüber safe_load_all:

  • yaml.safe_load(text) erwartet genau ein Dokument. Enthält der Text mehrere (durch --- getrennte) Dokumente, führt das zu einem Fehler.
  • yaml.safe_load_all(text) ist für mehrere Dokumente gedacht. Es liest sie nicht alle auf einmal ein, sondern liefert einen Iterator: ein Objekt, das die Dokumente nacheinander bereitstellt – pro ----Abschnitt ein Dokument. Jedes gelieferte Dokument ist eine eigene Python-Datenstruktur (meist ein dict).

Üblicherweise wird dieser Iterator mit einer for-Schleife durchlaufen, die jedes Dokument einzeln verarbeitet. Werden alle Dokumente zugleich als Liste benötigt, lässt sich der Iterator mit list(...) einsammeln: dokumente = list(yaml.safe_load_all(text)); danach ist etwa dokumente[0] das erste Dokument.

Im folgenden Beispiel ergänzt enumerate(...) zusätzlich einen Zähler, sodass neben dem Dokument auch dessen laufende Nummer (beginnend bei 0) ausgegeben wird.

In [11]:
import yaml

doc = """
---
name: dokument_1
typ: config
---
name: dokument_2
typ: secret
"""
# safe_load_all liefert die Dokumente nacheinander; enumerate ergaenzt einen Zaehler:
for nummer, dokument in enumerate(yaml.safe_load_all(doc)):
    print(nummer, dokument)
0 {'name': 'dokument_1', 'typ': 'config'}
1 {'name': 'dokument_2', 'typ': 'secret'}

Beziehung zwischen YAML und JSON

Da JSON eine Teilmenge von YAML ist, kann der YAML-Parser JSON unmittelbar einlesen. Das ist für Konvertierungen praktisch: JSON hinein, YAML heraus (und umgekehrt).

In [12]:
import yaml

# JSON ist gueltiges YAML und laesst sich daher direkt mit safe_load einlesen:
json_text = '{"name": "webservice", "ports": [8080, 8443], "enabled": true}'
data = yaml.safe_load(json_text)
print("aus JSON eingelesen:", data)

# und wieder als (gut lesbares) YAML ausgeben:
print("--- als YAML ---")
print(yaml.dump(data, sort_keys=False, default_flow_style=False))
aus JSON eingelesen: {'name': 'webservice', 'ports': [8080, 8443], 'enabled': True}
--- als YAML ---
name: webservice
ports:
- 8080
- 8443
enabled: true

YAML mit Python: PyYAML im Detail

  • yaml.safe_load(text) → Python-Datenstruktur (sicher, empfohlen).
  • yaml.safe_load_all(text) → liefert nacheinander mehrere Dokumente.
  • yaml.safe_dump(obj) bzw. yaml.dump(obj) → erzeugt YAML-Text.

Sicherheit – wichtig: Bei Daten aus nicht vertrauenswürdiger Quelle ist stets safe_load zu verwenden, niemals das ältere yaml.load(text) ohne Angabe eines Loader. Das vollständige load kann über Tags beliebige Python-Objekte erzeugen (bis hin zur Ausführung von Code). safe_load lässt nur die Standard-Datentypen zu.

Nützliche Optionen von dump:

  • sort_keys=False – behält die Reihenfolge der Schlüssel bei.
  • default_flow_style=False – erzwingt den (lesbareren) Block-Stil.
  • allow_unicode=True – gibt Umlaute und andere Unicode-Zeichen direkt aus, statt sie zu maskieren.

Soll beim Bearbeiten der Kommentar erhalten bleiben (ein sogenannter Round-Trip), ist die Bibliothek ruamel.yaml besser geeignet – PyYAML verwirft Kommentare beim Einlesen.

In [13]:
import yaml

obj = {
    "name": "wörterbuch",
    "ports": [8080, 8443],
    "database": {"host": "db.example.com", "port": 5432},
}

# Standardausgabe: schlechter lesbar (Schluessel sortiert, Unicode maskiert)
print(yaml.dump(obj))
print("=== besser lesbar ===")
print(yaml.dump(obj, sort_keys=False, default_flow_style=False, allow_unicode=True))
database:
  host: db.example.com
  port: 5432
name: "w\xF6rterbuch"
ports:
- 8080
- 8443

=== besser lesbar ===
name: wörterbuch
ports:
- 8080
- 8443
database:
  host: db.example.com
  port: 5432

Häufige Fehlerquellen und Empfehlungen

  • Tabulatoren zum Einrücken führen zu Fehlern. Immer Leerzeichen verwenden, konsistent (meist zwei).
  • Automatische Typerkennung wird leicht unterschätzt: NO, yes, 1.10, 01067 in Anführungszeichen setzen, wenn eine Zeichenkette gemeint ist.
  • Doppelpunkt im Wert ohne Anführungszeichen (etwa url: http://x:8080) kann zu Verwirrung führen → in Anführungszeichen setzen.
  • Einrückung von Listen wird leicht verwechselt → die Felder eines Listen-Mappings auf gleiche Tiefe setzen.
  • Kommentare gehen bei einem Round-Trip mit PyYAML verloren → bei Bedarf ruamel.yaml verwenden.
  • Sicherheit: für fremde Daten ausschließlich safe_load einsetzen.

Dasselbe Datenobjekt in JSON, XML und YAML

Zum direkten Vergleich derselbe Inhalt in allen drei Formaten.

JSON

{
  "name": "webservice",
  "enabled": true,
  "ports": [8080, 8443],
  "database": {"host": "db.example.com", "port": 5432}
}

XML

<webservice enabled="true">
  <name>webservice</name>
  <ports>
    <port>8080</port>
    <port>8443</port>
  </ports>
  <database host="db.example.com" port="5432"/>
</webservice>

YAML

name: webservice
enabled: true
ports:
  - 8080
  - 8443
database:
  host: db.example.com
  port: 5432

Gut erkennbar: YAML ist am kompaktesten und am besten lesbar, XML am ausführlichsten.

In [14]:
import json, yaml

# Ausgangspunkt ist eine Python-Datenstruktur; gezeigt werden JSON und YAML:
obj = {
    "name": "webservice",
    "enabled": True,
    "ports": [8080, 8443],
    "database": {"host": "db.example.com", "port": 5432},
}

print("===== JSON =====")
print(json.dumps(obj, indent=2))
print("\n===== YAML =====")
print(yaml.dump(obj, sort_keys=False, default_flow_style=False))
===== JSON =====
{
  "name": "webservice",
  "enabled": true,
  "ports": [
    8080,
    8443
  ],
  "database": {
    "host": "db.example.com",
    "port": 5432
  }
}

===== YAML =====
name: webservice
enabled: true
ports:
- 8080
- 8443
database:
  host: db.example.com
  port: 5432

Übungen

Die Aufgaben sind in den darunterliegenden Code-Zellen zu bearbeiten. Hilfreich sind yaml.safe_load und json.loads.

Aufgabe 1 – Formate erkennen

Die folgenden drei Snippets beschreiben jeweils denselben Inhalt, aber in unterschiedlichen Formaten. Für jedes Snippet (A, B, C) ist anzugeben, ob es sich um JSON, XML oder YAML handelt, und die Entscheidung ist kurz zu begründen (woran ist das Format erkennbar?).

Snippet A

name: webservice
ports:
  - 8080
  - 8443

Snippet B

<webservice>
  <name>webservice</name>
  <ports>
    <port>8080</port>
    <port>8443</port>
  </ports>
</webservice>

Snippet C

{"name": "webservice", "ports": [8080, 8443]}

Aufgabe 2 – YAML nach Python

Das folgende YAML ist einzulesen. Auszugeben sind die Liste aller Service-Namen sowie die Ports von web.

In [15]:
import yaml

aufgabe2 = """
services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
  db:
    image: postgres:16
"""
# Aufgabe: einlesen, danach Service-Namen und die Ports von web ausgeben

Aufgabe 3 – Stolperfalle finden

Das folgende YAML enthält ein Problem durch die automatische Typerkennung. Es ist einzulesen, der fehlinterpretierte Wert ist zu finden, und das YAML ist so zu korrigieren, dass alle Werte Zeichenketten bleiben.

In [16]:
import yaml

aufgabe3 = """
laender:
  - NO
  - SE
  - DK
plz: 01067
version: 1.10
"""
# Aufgabe: einlesen, Problem(e) erkennen, korrigierte Variante mit Zeichenketten erstellen
print(yaml.safe_load(aufgabe3))
{'laender': [False, 'SE', 'DK'], 'plz': 567, 'version': 1.1}

Aufgabe 4 – Anchors anwenden

Zu erstellen ist eine compose-ähnliche Struktur mit zwei Services, die sich eine gemeinsame Konfiguration (restart: always, logging: json) über einen Anchor und einen Merge Key teilen. Ein Service soll restart überschreiben. Das Ergebnis ist mit safe_load einzulesen und zu prüfen.

In [17]:
import yaml

# Aufgabe: YAML-Text mit &anchor und <<: *anchor erstellen und einlesen
aufgabe4 = """
"""
# print(yaml.safe_load(aufgabe4))

Aufgabe 5 – Fehlerhafte docker-compose.yml korrigieren

Die folgende Datei lässt sich nicht einlesen bzw. wird falsch interpretiert. Es sind alle Fehler zu finden (Einrückung, Verwechslung von Liste und Mapping, fehlende Anführungszeichen) und eine korrekte Fassung ist zu erstellen. Hinweis: yaml.safe_load löst bei Syntaxfehlern eine Ausnahme (Exception) aus; diese lässt sich abfangen und ihre Meldung lesen.

In [18]:
import yaml

kaputt = """
services:
  - web:
      image: nginx
      ports:
        - 8080:80
  db:
    image: postgres:16
     environment:
       POSTGRES_PASSWORD: geheim
"""
try:
    print(yaml.safe_load(kaputt))
except yaml.YAMLError as e:
    print("YAML-Fehler:", e)

# Aufgabe: korrigierte Fassung in 'korrekt' erstellen und erfolgreich einlesen
korrekt = """
"""
YAML-Fehler: while parsing a block collection
  in "<unicode string>", line 3, column 3:
      - web:
      ^
expected <block end>, but found '?'
  in "<unicode string>", line 7, column 3:
      db:
      ^

Aufgabe 6 – Konvertierung JSON ↔ YAML

Das folgende JSON ist einzulesen und anschließend als gut lesbares YAML auszugeben (sort_keys=False, Block-Stil, allow_unicode=True). Danach ist es wieder nach JSON zurückzuwandeln.

In [19]:
import json, yaml

aufgabe6 = '{"name": "wörterbuch", "ports": [8080, 8443], "db": {"host": "x", "port": 5432}}'
# Aufgabe: JSON -> Python -> als YAML ausgeben -> wieder nach JSON

Zusammenfassung und weiterführende Verweise

Wann welches Format?

  • JSON – Datenaustausch, Web-APIs, einfache Strukturen, maschinennah.
  • XML – Dokumente mit gemischtem Text und Auszeichnung, etablierte Standards im Unternehmensumfeld, starke Validierung und Transformation.
  • YAML – Konfigurationsdateien für Menschen (Docker Compose, Kubernetes, CI/CD, Ansible).

Wichtige YAML-Punkte

  • Einrückung mit Leerzeichen (keine Tabulatoren); üblich sind zwei Leerzeichen.
  • Mappings (schlüssel: wert) und Sequenzen (- element); Block- gegenüber Flow-Stil.
  • Vorsicht bei der automatischen Typerkennung (NO, yes, 1.10, Port-Zuordnungen) → im Zweifel in Anführungszeichen setzen.
  • Mehrzeilige Zeichenketten mit | (literal) und > (folded).
  • Wiederverwendung mit &anchor, *alias, << (Merge Key); in compose oft über x--Extension-Fields.
  • Mehrere Dokumente mit ---.
  • In Python: stets safe_load verwenden; zum Erhalt von Kommentaren ruamel.yaml.

Spezifikationen und Verweise