Eine Lieferung, ein Vertrag, drei eindeutige Ergebnisse
JSON-Dateien automatisch validieren: von JSON Schema zum CI-Test
Eine Liefermeldung enthält "quantity": 3. Die nächste liefert an derselben Stelle "three". Eine dritte Datei bricht mitten im JSON ab. Alle drei lassen sich automatisch prüfen – aber sie dürfen nicht dieselbe Antwort bekommen.
Genau dafür bauen wir den ersten Baustein von ZEUS Interface Quality. Er liest eine lokale JSON-Datei, prüft sie gegen einen vereinbarten Vertrag und liefert einen Bericht samt Exit-Code. Hier kannst du das Ergebnis nachstellen und nachvollziehen, welche Entscheidungen aus einem einfachen Validator einen verlässlicheren Test machen.
Die größere Frage – wie wir APIs, Dateien und Legacy-Schnittstellen automatisch testen wollen – steht im Hauptartikel zur automatischen Schnittstellenqualität. Dieser Praxisartikel bleibt bewusst bei der abgeschlossenen ersten Iteration.

Was diese Iteration liefert
Die CLI unterscheidet drei Fälle. Ein Exit-Code ist dabei die Zahl, mit der ein Programm seinem aufrufenden Skript mitteilt, wie der Lauf ausgegangen ist.
- 0 – gültig: Das JSON-Dokument erfüllt das ausgewählte Schema.
- 1 – Vertragsverletzung: Das Dokument ist lesbar, aber mindestens eine Schema-Regel ist verletzt.
- 2 – Prüfung nicht möglich: Zum Beispiel ist eine Datei unlesbar, das JSON beschädigt, das Schema fehlerhaft oder ein Schema-Verweis nicht erlaubt.
Vorhanden sind Text- und JSON-Ausgabe, feste Testdaten und eine CI-Prüfung. Nicht vorhanden sind HTTP- oder SFTP-Abruf, CSV, XML, YAML, fachliche Zusatzregeln und Tests gegen echte Gegenstellen.
Der Vertrag für unsere Liefermeldung
Wir beginnen mit einer kleinen, bewusst überschaubaren Lieferung. Die Beispieldaten sind synthetisch und liegen als Fixtures im Repository: fest gespeicherte Eingaben, die bei jedem Test wiederverwendet werden.
{
"deliveryId": "DEL-1001",
"quantity": 3,
"email": "ops@example.org",
"address": {
"street": "Hauptstraße 1",
"postalCode": "10115",
"city": "Berlin"
}
}
Das passende Schema legt fest, welche Felder vorhanden sein müssen und welche Werte erlaubt sind. Die folgende Darstellung entspricht dem Inhalt unserer Schema-Datei; lediglich die Formatierung ist kompakter.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "delivery.schema.json",
"title": "Delivery",
"type": "object",
"additionalProperties": false,
"required": ["deliveryId", "quantity", "email", "address"],
"properties": {
"deliveryId": { "type": "string", "minLength": 1 },
"quantity": { "type": "integer", "minimum": 1 },
"email": { "type": "string", "format": "email" },
"address": { "$ref": "refs/address.schema.json" }
}
}
quantity muss eine ganze Zahl von mindestens eins sein. Die Zeichenkette "three" erfüllt das nicht. additionalProperties: false verbietet zusätzliche Felder in diesem Objekt. Das ist eine Entscheidung dieses Vertrags, keine pauschale Vorgabe des Werkzeugs.
Die Lieferadresse hat einen eigenen Vertrag in refs/address.schema.json. Er verlangt Straße, Postleitzahl und Stadt. Die fünfstellige Postleitzahl ist eine bewusst enge Beispielregel, kein internationales Adressmodell. Beim Klonen erhältst du beide Schema-Dateien und alle Eingaben vollständig.
Formatangabe heißt nicht automatisch Formatprüfung
In Draft 2020-12 kann format lediglich eine Annotation sein. Wir aktivieren die Format-Assertions ausdrücklich. Deshalb wird "not-an-email" als Fehler erkannt. Geprüft wird das Format, nicht die Existenz oder Erreichbarkeit eines Postfachs. Das folgt der Unterscheidung im JSON-Schema-Standard und der Konfiguration des verwendeten Validators.
JSON-Dateien selbst automatisch validieren
Du brauchst Git und ein JDK 21. Maven wird über den mitgelieferten Wrapper gestartet; eine separate Maven-Installation ist nicht erforderlich. Für den ersten Build werden Maven und Abhängigkeiten heruntergeladen. Die anschließende Validierung mit dem fertigen JAR und lokalen Schemas benötigt keinen Netzzugriff.
Damit spätere Änderungen auf main die Beispiele nicht verändern, verwenden wir den geprüften Commit d794151. Führe die Schritte nacheinander aus und gehe nur weiter, wenn Klonen und Build erfolgreich waren.
1. Den Artikelstand in ein neues Verzeichnis klonen
Diese Befehle funktionieren in PowerShell sowie in einer Bash-Shell. Wähle einen Arbeitsordner, in dem zeus-interface-quality-iteration-1 noch nicht existiert. Ein vorhandener Projekt-Checkout bleibt so unberührt.
git clone https://github.com/gzeuner/zeus-interface-quality.git zeus-interface-quality-iteration-1
cd zeus-interface-quality-iteration-1
git checkout --detach d79415111b7d830f2aa4741f7bec647a2652535c
Der Hinweis auf einen „detached HEAD“ ist hier erwartbar: Du betrachtest einen festen Commit statt eines beweglichen Branches. Alle folgenden Pfade beziehen sich auf dieses Repository-Verzeichnis.
2. Unter Windows bauen und prüfen
In PowerShell erstellt der erste Befehl das ausführbare JAR und führt die Tests aus:
.\mvnw.cmd -B package
Nach einem erfolgreichen Build starten wir die drei Fälle. Lies $LASTEXITCODE jeweils unmittelbar nach dem Java-Aufruf aus.
$iqJar = "target/zeus-interface-quality-0.1.0-SNAPSHOT.jar"
$iqFixtures = "src/test/resources/fixtures"
java -jar $iqJar validate --schema "$iqFixtures/delivery.schema.json" --input "$iqFixtures/valid-delivery.json" --report json
$LASTEXITCODE
java -jar $iqJar validate --schema "$iqFixtures/delivery.schema.json" --input "$iqFixtures/invalid-wrong-type.json" --report json
$LASTEXITCODE
java -jar $iqJar validate --schema "$iqFixtures/delivery.schema.json" --input "$iqFixtures/malformed.json" --report json
$LASTEXITCODE
Alternative: unter Linux oder macOS
In Bash nutzt du den anderen Wrapper-Aufruf und liest den Exit-Code mit $?. Diese Variante ersetzt die beiden PowerShell-Blöcke. Die erwarteten Codes bleiben 0, 1 und 2. In einem Skript mit aktivem set -e müssen die absichtlich fehlschlagenden Aufrufe gesondert behandelt werden; zum Ausprobieren sind die Zeilen für eine interaktive Shell gedacht.
./mvnw -B package
iq_jar="target/zeus-interface-quality-0.1.0-SNAPSHOT.jar"
iq_fixtures="src/test/resources/fixtures"
java -jar "$iq_jar" validate --schema "$iq_fixtures/delivery.schema.json" --input "$iq_fixtures/valid-delivery.json" --report json
echo "$?"
java -jar "$iq_jar" validate --schema "$iq_fixtures/delivery.schema.json" --input "$iq_fixtures/invalid-wrong-type.json" --report json
echo "$?"
java -jar "$iq_jar" validate --schema "$iq_fixtures/delivery.schema.json" --input "$iq_fixtures/malformed.json" --report json
echo "$?"
Mit --report text erhältst du eine lesbare Konsolenausgabe; ohne Formatoption ist Text bereits der Standard. Bei malformed.json erscheint eine Fehlermeldung auf stderr, dem Fehlerausgabekanal. Es gibt in diesem Fall keinen JSON-Bericht auf stdout. Auch ein fehlender Eingabe- oder Schemapfad führt zu Exit-Code 2.
Der dokumentierte JAR-Stand kann zusätzlich einen SLF4J-Hinweis zum fehlenden Logging-Provider auf stderr ausgeben. Er ist kein Validierungsbefund und verändert den Exit-Code nicht. Für eine maschinelle Auswertung bleiben Ergebnis- und Fehlerausgabe getrennt.
Den JSON-Bericht richtig lesen
Die gültige Lieferung ergibt status: VALID, valid: true und eine leere Liste findings. Für den falschen Typ liefert die CLI auf stdout dagegen diesen Bericht:
{
"status": "INVALID",
"valid": false,
"findings": [
{
"instancePath": "/quantity",
"schemaPath": "/properties/quantity/type",
"keyword": "type",
"message": "string gefunden, integer erwartet"
}
]
}
instancePath zeigt die betroffene Stelle in den Daten. keyword nennt die verletzte Regel. schemaPath bezeichnet im aktuellen Adapter den Auswertungspfad des Validators. Im gezeigten Fall führt er zur Typregel; bei Verweisen kann er auch $ref-Abschnitte enthalten. Er ist daher nicht immer direkt als Pfad in der obersten Schema-Datei auflösbar.
Ein leerer Datenpfad "" bedeutet „Wurzel des Dokuments“. Das ist etwa beim unerlaubten zusätzlichen Feld der Fall: Die Regel betrifft das umgebende Objekt. Ein leerer Pfad ist dort kein fehlender Fehlerhinweis.
Alle Findings werden nach Datenpfad, Schema-Auswertungspfad, Schlüsselwort und Meldung sortiert. Die Meldungen sind in diesem Stand ausdrücklich deutsch, unabhängig von der Sprache des Rechners. Für Automatisierung verwenden wir Status, Pfade und Schlüsselwörter; der genaue Wortlaut ist kein Ersatz für diese Felder. Den Berichtsvertrag v1 prüfen wir zusätzlich gegen ein fest gespeichertes Beispiel.
Was beim Review noch fehlte
Die ersten Tests für gültige und ungültige Lieferungen waren grün. Das genügte noch nicht. Ein Validator muss auch mit Eingaben umgehen, die der Happy Path nie erzeugt.
Die gesamte Datei zählt
Ein Parser darf nicht nur das erste Objekt lesen und nachfolgende Daten übersehen. Wir lehnen deshalb leere Dateien, reine Leerzeichen, zusätzlichen Inhalt nach einem JSON-Dokument und doppelte Objektschlüssel ab. Das sind technische Fehler mit Exit-Code 2. Der echte JSON-Wert null bleibt dagegen ein gültiges JSON-Dokument; ob er erlaubt ist, entscheidet das Schema.
Doppelte Schlüssel sind besonders tückisch: Wenn ein Parser einen Wert still durch den späteren ersetzt, sieht die Schema-Prüfung die ursprüngliche Mehrdeutigkeit nicht mehr. Unsere Entscheidung fällt deshalb bereits beim Einlesen, nicht erst im Schema.
Auch der Vertrag kann falsch sein
Ein Objekt mit "required": "deliveryId" kann syntaktisch zulässiges JSON sein, verwendet das Schema-Schlüsselwort aber falsch: required erwartet eine Liste. Deshalb prüfen wir die Schema-Dateien selbst gegen das mitgelieferte Draft-2020-12-Meta-Schema. Das gilt auch für nachgeladene lokale Schema-Dateien. Ein defekter Vertrag darf nicht wie eine fehlerhafte Lieferung behandelt werden.
Der echte Aufruf muss zum Beispiel passen
Ein Test des Berichtsschreibers allein beweist noch nicht, dass die CLI denselben Inhalt erzeugt. Deshalb vergleicht ein durchgängiger Test das Ergebnis des tatsächlichen validate-Befehls mit dem eingefrorenen Bericht – auch bei englischer Host-Locale. Das schützt vor unbemerkten Änderungen zwischen Parser, Validator und Ausgabe.
Die wichtigste Erkenntnis: Nicht die Anzahl grüner Tests entscheidet, sondern welche Fehlerannahmen sie tatsächlich prüfen.
Warum Schema-Verweise lokal bleiben
Ein $ref spart doppelte Definitionen, kann aber auch zusätzliche Dateien oder Netzwerkzugriffe ins Spiel bringen. Für Iteration 1 gilt deshalb eine klare Grenze: Nachgeladene Schemas müssen reguläre Dateien innerhalb des tatsächlichen Verzeichnisses des ausgewählten Hauptschemas sein.
Geprüft werden sowohl der normalisierte Pfad als auch das aufgelöste Ziel. Ein symbolischer Link darf die Verzeichnisgrenze nicht umgehen. HTTP-Verweise und Dateiverweise mit fremdem Host werden vor dem Abruf abgewiesen. Die Schemas werden außerdem vor der Datenvalidierung initialisiert, damit ein fehlender Verweis nicht allein deshalb unentdeckt bleibt, weil ein optionales Feld gerade fehlt.
Die HTTPS-Adresse bei $schema ist kein Widerspruch: Sie benennt den Dialekt. Das dazugehörige Meta-Schema bringt die Bibliothek bereits mit; dafür muss unser Prüfablauf nicht ins Internet. Die Umsetzung ist im JSON-Schema-Adapter nachvollziehbar.
Eine klare Grenze ist noch keine Sandbox
Dieser Stand setzt kontrollierte lokale Eingaben und Schema-Verzeichnisse voraus. Er garantiert weder Schutz gegen absichtlich während des Lesens veränderte Dateien noch feste Ressourcen- oder Laufzeitgrenzen für feindliche Eingaben. Daraus folgt noch keine Freigabe für einen öffentlich erreichbaren Validierungsdienst.
Technologieoffen mit einem kleinen Java-Kern
Wir haben uns für Java 21 entschieden und trotzdem kein großes Framework-Gerüst aufgebaut. Ein Maven-Modul genügt. Die Aufteilung erfolgt zunächst über klar abgegrenzte Pakete:
| Baustein | Aufgabe |
|---|---|
core |
Eigene Ergebnisobjekte, feste Sortierung und eine Validator-Schnittstelle; keine NetworkNT-Typen im Vertrag |
adapter.jsonschema |
Jackson liest JSON; NetworkNT prüft die Schemas und Daten |
report |
Schreibt den vereinbarten Text- oder JSON-Bericht |
cli |
Picocli verarbeitet Optionen und bildet Ergebnis oder Fehler auf Exit-Codes ab |
„Technologieoffen“ heißt hier nicht, dass Java-Code plötzlich sprachneutral wäre. Es heißt: Bibliotheksdetails bleiben hinter unseren Schnittstellen. Wir können einen Validator oder einen Ausgabeweg gezielt ersetzen. Ein weiterer Adapter muss sich erst an einem konkreten Anwendungsfall beweisen.
Spring Boot ist nicht grundsätzlich ausgeschlossen. Für diese lokale Prüfung benötigen wir aber weder einen Server noch dessen Betriebsumgebung. Sollte später ein Dienst sinnvoll sein, kann er den Kern einbinden. Ihn schon jetzt einzubauen würde eine Entscheidung vorwegnehmen, die Iteration 1 noch nicht braucht.
Was der automatisierte Nachweis zeigt
Der GitHub-Actions-Lauf zu Commit d794151 besteht unter Linux alle 36 Tests ohne Fehler oder übersprungene Fälle. Lokal unter Windows bestanden 34; zwei Tests für symbolische Links wurden wegen fehlender Berechtigungen übersprungen. Gerade deshalb ist der zusätzliche Linux-Nachweis wichtig.
Die Pipeline führt Tests aus, baut das Paket und startet anschließend das tatsächlich erzeugte JAR mit einer gültigen und einer ungültigen Fixture. Beim absichtlich ungültigen Fall muss der Smoke-Test ausdrücklich Exit-Code 1 bestätigen. Einfach jeden Fehler zu ignorieren würde den Nachweis wertlos machen.
Das belegt den vereinbarten lokalen Ablauf – keinen vollständigen Schnittstellentest. Eine echte SFTP-Gegenstelle, ein HTTP-Endpunkt, fachliche Regeln und Lastverhalten sind nicht Teil dieses Nachweises. Auch ein erster Release und die zugehörige Lizenzentscheidung stehen noch aus.
Fazit: Der erste Baustein steht
Wir können JSON-Dateien automatisch validieren und dabei drei Dinge zuverlässig auseinanderhalten: gültige Daten, Vertragsverletzungen und technische Fehler. Schema, Fixtures, Bericht und CLI gehören zu einem reproduzierbaren Stand.
Der wertvollste Teil der Iteration war das Nachschärfen: die gesamte Datei lesen, auch das Schema prüfen, Verweise begrenzen und den echten Aufruf testen. Das ist ein sinnvoller Abschluss von Iteration 1 – kein Anlass, nebenbei schon Iteration 2 zu beginnen.
Wie sich dieser Baustein in die größere Idee einordnet und welche Fragen offenbleiben, zeigt die Project-Page „Schnittstellen automatisch testen“.
Code und Quellen
Stand: 15. September 2026. Die Beispiele sind an Commit d79415111b7d830f2aa4741f7bec647a2652535c gebunden, nicht an den jeweils neuesten Stand des Projekts.



tiny-tool.de
tiny-tool.de