JSON-Validierung in CI: eine Pipeline mit jsonlint, ajv und jq
Fazit: Syntaxvalidierung beweist, dass eine Datei parst; Schema-Validierung beweist, dass Felder dem Vertrag folgen. In CI zählen eindeutige Exit-Codes, Fehlerstellen und Reparaturwege.

Kurz gesagt: In CI lässt man jq empty die Syntax erledigen, ajv-cli die Struktur (check-jsonschema für Teams mit mehreren Formaten) und Prettier den Stil; verketten Sie sie schichtweise und stoppen Sie beim ersten Fehlschlag.
Dass der Parser in Ihrem lokalen Editor durchgeht, heißt nicht, dass die Datei in CI gehört; dass CI besteht, heißt auch nicht, dass die Felder zum Vertrag passen.
1. Syntaxvalidierung von Schema-Validierung trennen
Der Ausdruck „JSON-Validierung“ wird viel zu weit verwendet, weshalb Teams oft ein Werkzeug wählen, um zwei verschiedene Probleme zu lösen:
| Schicht | Beantwortete Frage | Typische Werkzeuge | Was ein Fehlschlag bedeutet |
|---|---|---|---|
| Syntax | Ist diese Datei gültiges JSON? | jq empty, jsonlint, python -m json.tool |
Klammern, Kommas, Anführungszeichen oder Kodierung sind kaputt |
| Schema | Passen Felder, Typen und Pflichteigenschaften zum Vertrag? | ajv-cli, check-jsonschema |
Die Datei parst, aber die Geschäftsstruktur ist falsch |
| Stil | Sind Einrückung und Schlüsselreihenfolge einheitlich? | Prettier, jq -S |
Formatierung uneinheitlich und Diffs rauschend |
Diese Schichten können einander nicht ersetzen. Eine Datei, die die Syntax besteht, kann trotzdem am Schema scheitern: {"price": "12.5"} ist gültiges JSON, verlangt das Schema aber price als Zahl, ist es nicht akzeptabel. Umgekehrt wird eine Datei mit fehlender schließender Klammer nie gegen ein noch so vollständiges Schema kompilieren.
Die Pipeline sollte also zuerst Syntax, dann Struktur und zuletzt Stil ausführen. Stoppen Sie, sobald eine Schicht scheitert, sonst tarnt sich ein Parse-Fehler als Feldfehler.
2. Syntaxschicht: jq empty ist die Option mit minimalen Abhängigkeiten
Ist jq bereits im CI-Image, kostet Syntaxvalidierung fast nichts:
jq empty data.json
jq empty erzeugt keine Ausgabe; es parst nur die Datei. Bei Erfolg endet es mit 0, bei Fehlschlag mit einem Nicht-Null-Code plus Zeilen-/Spalteninformation. Das macht es leichter als einen vollständigen Node-Abhängigkeitsbaum zu ziehen, und zu einem guten ersten Gate.
Beim Batch-Validieren von JSON-Dateien in einem Repository node_modules, Build-Ausgaben und Drittanbieter-Caches ausschließen:
find . -name '*.json' \
-not -path './node_modules/*' \
-not -path './dist/*' \
-print0 | xargs -0 -n1 jq empty
-print0 und xargs -0 behandeln Leerzeichen in Dateinamen, während -n1 eine Datei pro Aufruf sicherstellt, damit die Fehlermeldung den Übeltäter nennt. Übergeben Sie nicht Hunderte von Dateien an einen einzigen Prozess; Fehlermeldungen überschreiben sich sonst.
jsonlint ist eine andere verbreitete Wahl, meist als npx jsonlint -q data.json aufgerufen, wobei -q nur Fehler ausgibt. Es meldet Zeilen- und Spaltennummern, aber seine Wartung ist weniger aktiv als bei jq, und verschiedene Versionen tolerieren BOM und Kommentare unterschiedlich. Behandeln Sie es als lesbareren Syntaxprüfer, nicht als Schema-Validator.
3. Schema-Schicht: die Grundlagen von ajv-cli validate
Ajv ist der am weitesten verbreitete Schema-Validator im JavaScript-Ökosystem, und ajv-cli exponiert ihn als Kommandozeilenwerkzeug:
ajv validate -s schema.json -d data.json
-s zeigt auf die Schemadatei, -d auf die zu validierenden Daten. Bei Fehlschlag endet der Prozess mit Nicht-Null, CI scheitert also sofort. Reale Projekte ergänzen meist zwei Flags:
npx ajv-cli validate \
-s schemas/order.json \
-d 'fixtures/orders/*.json' \
--strict=false \
--errors=text
--strict=false hilft bei älteren Schemas: Der Strict Mode behandelt unbekannte Schlüsselwörter und verdächtige required-Deklarationen als Fehler, was viele Legacy-Schemas scheitern lässt. --errors=text erzeugt menschenlesbare Ausgabe, während die standardmäßige JSON-Struktur besser für Maschinen ist. Wählen Sie danach, wer das Ergebnis konsumiert.
Auch die Draft-Version des Schemas muss passen. Ajv-Hauptversionen unterstützen standardmäßig verschiedene Drafts, und ajv-cli kann mit --spec=draft7, --spec=draft2019 oder --spec=draft2020 einen auswählen. Deklariert ein Schema $schema: "https://json-schema.org/draft/2020-12/schema", die CLI kompiliert es aber als draft-07, werden manche Schlüsselwörter stillschweigend ignoriert. Mehr zu Schemaerstellung und Versionsunterschieden siehe Erste Schritte mit JSON Schema.
Formatvalidierung ist eine weitere häufige Anforderung. Das Schlüsselwort format ist standardmäßig nur Annotation; damit email, date-time und uri tatsächlich etwas erzwingen, laden Sie ajv-formats:
npx ajv-cli validate -c ajv-formats -s schema.json -d data.json
Dieser Schritt wird leicht übersehen, und das Ergebnis ist, dass ein E-Mail-Feld not-an-email akzeptiert. Geben Sie der Pipeline vor der Auslieferung einen absichtlich ungültigen Datensatz; das beweist weit besser, dass die Regel aktiv ist, als die Dokumentation zehnmal zu lesen.
check-jsonschema: mehrere Formate und freundlichere Fehler
Verarbeitet das Team neben JSON auch YAML, TOML oder GitHub-Actions-Konfiguration, ist check-jsonschema bequemer:
check-jsonschema --schemafile schemas/config.json config/app.json
check-jsonschema --schemafile schemas/pipeline.json .github/workflows/ci.yml
Seine praktischen Vorteile:
- Native YAML-Unterstützung, die zu Dateien passt, die „JSON-förmig, aber in YAML geschrieben“ sind, etwa Kubernetes-Manifeste und CI-Konfiguration.
- Fehler enthalten Dateipfad, Zeilennummer und Feldpfad, was die Lokalisierungskosten niedrig hält.
- Ein pre-commit-Hook wird bereitgestellt, die Integration ist also billig.
- Es kann gängige Schemas aus SchemaStore ziehen, akzeptiert aber auch lokale Dateien, was eine Netzwerkabhängigkeit vermeidet.
In sprachübergreifenden Teams ist Python oft schon vorhanden, check-jsonschema zu installieren und zu aktualisieren ist also leichter, als eine Node-Toolchain zu pflegen. Lassen Sie jq die Syntax und dieses Werkzeug die Struktur erledigen; die Kombination hat sehr geringen Wartungsaufwand.
4. Exit-Codes sind das einzige verlässliche CI-Signal
So poliert die Ausgabe auch ist, CI liest nur Exit-Codes. Drei Fallen sind verbreitet:
| Symptom | Ursache | Behebung |
|---|---|---|
| Es gibt Fehler, aber CI ist grün | Der Befehl endet mit ` | |
| Keine Fehler, aber CI ist rot | diff behandelt „Unterschiede gefunden“ als Fehlschlag |
„Unterschied“ von „Ausführungsfehler“ unterscheiden |
| Fehlerausgabe ist abgeschnitten | Parallele Mehrdatei-Ausgabe überschreibt sich | Seriell laufen lassen oder pro Datei ausgeben |
Merken Sie sich die Exit-Code-Semantik von jq: Normales Parsen ergibt 0; mit -e ergibt ein Ausdrucksergebnis false oder null den Wert 1; Nutzungsfehler und fehlende Dateien ergeben 2; Skriptkompilierungsfehler ergeben 3. Schema-Werkzeuge verwenden meist 0 für Erfolg, 1 für Validierungsfehler und 2 für Werkzeug- oder Argumentfehler. Kollabieren Sie in einem Wrapper nicht jeden Nicht-Null-Code zu „die Daten sind falsch“, sonst wird ein Umgebungsfehler als Inhaltsproblem fehldiagnostiziert.
5. pre-commit: Probleme vor dem Commit abfangen
Der Wert von pre-commit ist nicht „eine Prüfung mehr“; er ist, Entwicklern in einer Minute Feedback zu geben statt nach einem zehnminütigen CI-Lauf. Eine minimale Konfiguration sieht so aus:
repos:
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.33.0
hooks:
- id: check-jsonschema
files: ^configs/.*\.json$
args:
- --schemafile
- schemas/config.json
- repo: local
hooks:
- id: jq-syntax
name: jq syntax check
entry: jq empty
language: system
files: ^configs/.*\.json$
Zwei Details zählen:
filessollte nur die Verzeichnisse treffen, die validiert werden müssen. Führen Sie Hooks nicht gegennode_modulesoder generierte Dateien aus.- Der lokale Hook setzt voraus, dass
jqauf dem Entwicklerrechner installiert ist. Dokumentieren Sie diese Anforderung oder wechseln Sie zu einem containerisierten Hook.
Verwendet das Projekt bereits Husky und lint-staged, kombinieren Sie sie direkt:
{
"lint-staged": {
"configs/*.json": ["jq empty", "prettier --check"]
}
}
Denken Sie daran, dass lint-staged eine Liste gestagter Dateien übergibt, der Befehl also mehrere Pfade akzeptieren muss. Beides tun jq empty und prettier --check. Lassen Sie einen Hook nicht das ganze Repository scannen; Commits werden sonst merklich langsamer.
6. GitHub Actions: eine Pipeline zum Kopieren
Der folgende Workflow führt Syntaxvalidierung und Schema-Validierung aus und bewahrt den Bericht bei Fehlschlag:
name: json-validate
on:
push:
paths:
- 'schemas/**'
- 'fixtures/**'
pull_request:
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Syntax check
run: |
find fixtures -name '*.json' -print0 | xargs -0 -n1 jq empty
- name: Schema check
run: |
npx --yes ajv-cli validate \
-s schemas/order.json \
-d 'fixtures/orders/*.json' \
--strict=false \
--errors=text
Der paths-Filter verhindert, dass reine Dokumentationsänderungen die Validierung auslösen. npx --yes passt zu Repositories ohne festgelegte Abhängigkeiten; Produktionsprojekte sollten ajv-cli in devDependencies aufnehmen und die Version festnageln. Ohne Lock kann ein Upstream-Release, das den Standard-Draft oder den Strict Mode ändert, die Pipeline über Nacht rot machen.
7. Fehlerlokalisierung: von Zeilennummern zu Feldpfaden
Die Hälfte des Werts eines Validators ist Erkennung; die andere Hälfte ist Lokalisierung. Lernen Sie, drei Arten von Ausgabe zu lesen:
- Syntaxfehler:
jqmeldet Zeile und Spalte, oft mit einem nahen Ausschnitt. Korrigieren Sie zuerst Klammern und Kommas; verdächtigen Sie nicht sofort die Kodierung. - Schemafehler: Ajvs
instancePathzeigt auf die Datenstelle (etwa/items/0/price),schemaPathzeigt auf die Regel (etwa#/properties/items/items/properties/price/type), undkeywordnennt die verletzte Beschränkung. - Formatfehler: Scheitert eine
format-Prüfung, sagt die Meldung oft nur „must match format "email"“. Kombinieren Sie sie mitinstancePath, um das Feld zu identifizieren.
Kopieren Sie instancePath in den Untersuchungsdatensatz; es ist weit nützlicher als eine Wand aus JSON. Bei Arrays sagt der numerische Index auch, welcher Datensatz gescheitert ist. Ist der Geschäftsschlüssel intuitiver, ergänzen Sie danach eine Abbildungszeile wie items[0].id = ORD-042.
Häufige Schemafehler, etwa additionalProperties: false zu überstrapazieren oder oneOf-Zweige zu schreiben, die sich tatsächlich überlappen, werden in Häufige Fallstricke der JSON-Schema-Generierung behandelt. Eine falsche Validierungsregel ist gefährlicher als keine Validierung, weil sie ein falsches Sicherheitsgefühl erzeugt.
8. Werkzeugauswahltabelle
| Bedarf | Empfohlenes Werkzeug | Warum | Worauf achten |
|---|---|---|---|
| Nur Syntax | jq empty |
Null Abhängigkeiten, schnell, klare Exit-Codes | Leerzeichen in Dateinamen bei Batch-Läufen |
| Freundlichere Zeilen-/Spaltenfehler | jsonlint |
Lesbare Ausgabe | Wartungsaktivität ist begrenzt |
| JSON-Schema-Validierung | ajv-cli |
Ausgereiftes Ökosystem, strukturierte Fehler | Draft-Versionen abstimmen und format-Unterstützung laden |
| Gemischtes JSON-/YAML-Repository | check-jsonschema |
Mehrformate, pre-commit-freundlich | Schemaquelle und Netzwerkzugriff |
| Schnelles Feedback vor dem Commit | pre-commit / lint-staged | Verarbeitet nur gestagte Dateien | Hooks müssen mehrere Pfade akzeptieren |
| Einheitliche Formatierung | Prettier | Weit verbreitet | --check verwenden, wenn Sie Dateien nicht neu schreiben wollen |
Eine letzte Empfehlung: Prüfen Sie, dass die Pipeline tatsächlich scheitert, indem Sie ihr zuerst einen absichtlich ungültigen Datensatz geben. Das größte Risiko ist nicht eine komplizierte Konfiguration; es ist eine Konfiguration, die nie wirksam wird. „Sie kann scheitern“ als Abnahmekriterium zu behandeln ist verlässlicher als „sie besteht“. Zur breiteren Kommandozeilen-Toolchain lesen Sie weiter in Editoren, Kommandozeile und Automatisierung.
FAQ
Sollte ich jq empty oder jsonlint verwenden?
Bevorzugen Sie in CI jq empty: null Abhängigkeiten, schneller Start und klare Exit-Code-Semantik. Verwenden Sie jsonlint, wenn Sie freundlichere Zeilen-/Spaltenmeldungen brauchen oder lokal untersuchen. Beide führen nur Syntaxvalidierung aus; keines prüft die Feldstruktur.
Warum meldet ajv ein unbekanntes Schlüsselwort?
Bestätigen Sie zuerst, ob der $schema-Draft des Schemas zur Version passt, die die CLI verwendet. Viele „unbekanntes Schlüsselwort“-Fehler sind Schlüsselwörter neuer Drafts, auf die ein älterer Compiler stößt. Passen die Versionen, prüfen Sie auf einen Tippfehler. --strict=false kann Legacy-Schemas am Laufen halten, aber verlassen Sie sich nicht langfristig darauf, echte Probleme zu verbergen.
Schema-Validierung besteht, aber die API scheitert trotzdem. Warum?
Ein Schema validiert Struktur, nicht Geschäftssemantik. status mag etwa ein String mit zulässigem Enum sein, während die Zustandsmaschine verbietet, direkt von paid nach refunded zu gehen. Solche Regeln gehören in Code oder Datenbankbeschränkungen, nicht in JSON Schema.
Wie lokalisiere ich das scheiternde Feld schnell?
Lesen Sie instancePath oder die Zeilennummer aus der Werkzeugausgabe. Ajvs instancePath ist ein JSON Pointer, der direkt auf die Daten zeigt; check-jsonschema meldet Datei und Zeile. Notieren Sie den Pfad und bilden Sie ihn auf den Geschäftsschlüssel ab; die Lokalisierung dauert meist Minuten.
Sollte pre-commit eine vollständige Validierung ausführen?
Nein. pre-commit sollte nur gestagte Dateien verarbeiten, damit Commits schnell bleiben. Vollständige Scans gehören in CI oder geplante Jobs. Die Aufteilung ist einfach: lokale Läufe bevorzugen Geschwindigkeit, CI bevorzugt Vollständigkeit.