YAML

XML, JSON, CSV und INI sind seit 20 Jahren die Dateiformate, die ich am häufigsten in Projekten zur Datenspeicherung eingesetzt habe.

Seit etwa 10 Jahren kenne ich ein weiteres, nämlich YAML … aber erst heuer habe ich für dieses Format einen Parser geschrieben …


Das rekursive Akronym YAML steht für “YAML Ain’t Markup Language”, obwohl mir die originale “Yet Another Markup Language” besser gefallen hat.

Ähnlich wie in JSON kann man damit hierarchische Datenstrukturen als

  • Ja/Nein Werte (Boolean)
  • Zahlen (Float/Integer)
  • Text (Strings)
  • Listen (Arrays)
  • Key/Value Objekte (Dictionaries)

darstellen. Allerdings zeichnet sich YAML als besonders “human-readable” aus, da es nicht voraussetzt, dass die Dateneinträge durch Sonderzeichen wie Hochkomma und Klammern abgegrenzt werden, sondern es nutzt Text-Zeilen und Einrückungen (ähnlich wie Python) als primäres Zuordnungsraster.

 1version: 1.2.3
 2description: "Sample YAML document"
 3entry_array:
 4  - name:    "Entry object 1"
 5    id:      1234
 6        content: "Hello"
 7  - name:    "Entry object 2"
 8    id:      5678
 9    content: "World"
10meta_data:
11  author:    Me
12  created:   2020-12-06
13  published: Yes

Das gleiche Datenstruktur würde in JSON so aussehen:

 1{
 2  "version": "1.2.3",
 3  "description": "Sample YAML document",
 4  "entry_array": [
 5    { 
 6          "name": "Entry object 1",
 7      "id": 1234,
 8          "content": "Hello",
 9        },
10        {
11      "name": "Entry object 2",
12      "id": 5678,
13      "content": "World",
14        }
15  ],
16  "meta_data": 
17  {
18    "author": "Me",
19    "created": "2020-12-06",
20    "published": true
21  }
22}

Parser für Strings

Die meisten fertigen Parser haben aus meiner Perspektive zwei Nachteile:

  • Sie sind entweder an ein Framework gebunden, das viele Abhängigkeiten und Konventionen mitbringt.
  • Oder sie nutzen C char Strings oder C++ std::string und sind damit recht ineffizient.

Bei der Einführung von R-Value-References in C++11 wurde gemeldet, dass XML Komponenten um 400% schneller wurden, weil dort jedes Attribut als eigener String angelegt wurde.

Nun seit std::string_view in C++17 sollte das nochmals dramatisch schneller geworden sein, wenn dieses neue Feature korrekt benutzt wird.

Im GATE Framework sind string_view und string bereits zusammengefasst und daher verspricht eine eigene Implementierung basierend auf gate_string_t eine wesentlich höhere Effizienz als wenn man Fremdbibliotheken nutzt und danach Ende hin- und her konvertieren muss.

YAML zerteilen

Während ich mit JSON und XML keine so großen Schwierigkeiten hatte, stellte sich YAML als ungut harte Nuss heraus. Das Fehlen von Feldbegrenzern wie Hochkomma versetzt einen an vielen Stellen in den “Probier-mal” Modus. Ein "a b": "c d" wäre eindeutig und würde dem Member “a b” den Wert “c d” zuordnen. Doch in YAML lautet die Zeile a b : c d und die Leerzeichen vor und nach dem Doppelpunkt sind zu entfernen, während sie zwischen den Buchstaben erhalten bleiben müssen.

Auch sind Texte von Zahlen und Booleans schwerer zu unterscheiden. Die Zeile - 42 ist ein Arrayeintrag eines Integer während - 42 is the answer ein Stringeintrag sein sollte.

Aber am Schlimmsten sind Arrays in Objekten und Objekte in Arrays. Theoretisch unterscheiden sich Unterelemente von ihren Eltern durch eine tiefere Einrückung. Doch das führende - eines Array-Eintrag darf auf der gleichen Ebene wie das Elternelement sein, wenn dieses ein Objekt ist

1entries:
2- a: b
3  c: d
4- e: f
5  g: h

Im Beispiel hat der Member entries als Wert ein Array mit zwei Einträgen, die aus Objekten bestehen, einmal mit den Membern a und c und einmal mit e und g.
Dass das Minus auf der gleichen Einrück-Ebene wie das Elternobjekt liegt, muss man also speziell behandeln. Umgekehrt muss man auch verstehen, dass der Member c zum gleichen Objekt wie a gehört (genau so wie g und e).

Und dann gibt es noch eine Art Inline-Format, wo innerhalb von YAML quasi wieder JSON gesprochen wird (aber auch nur mit optionalen Anführungszeichen).

1entries: [ { a: b, c: d }, { e: f, g: h } ]

Muha! Was für ein Kack!

Fazit

Noch muss ich einige Tests mit meinem YAML Parser machen. Einige Features wie Referenzen zwischen Objekten werde ich gar nicht unterstützen, weil der kleinste gemeinsame Nenner zwischen JSON und YAML für mich als Richtwert gilt. Am Ende sollen beide Datenformate gleichwertig gelesen und in einen Objektbaum geladen werden können und umgekehrt auch wieder zurückgeschrieben werden können.

Da gehen zwangsläufig ein paar Features verloren.

Doch ich habe schwer unterschätzt, wie komplex das Parsing eines scheinbar “einfacheren” Dateiformats sein kann. Hier gilt tatsächlich wieder der Grundsatz:

Je leichter etwas für Menschen lesbar ist, um so schwieriger tun sich die Maschinen damit … und umgekehrt ebenso.

Wie auch immer … jetzt kann ich endlich auch bei YAML klugscheißen, wenn das Thema angesprochen wird.
Den Parser selbst schreiben ist also immer eine gute Idee.