Walidacja JSON w CI: potok z jsonlint, ajv i jq
Podsumowanie: Walidacja składni dowodzi, że plik się parsuje; walidacja schematem dowodzi, że pola są zgodne z kontraktem. W CI liczą się jednoznaczne kody wyjścia, lokalizacje błędów i ścieżki naprawy.

Najważniejsze: w CI pozwól jq empty obsłużyć składnię, ajv-cli obsłużyć strukturę (check-jsonschema dla zespołów wieloformatowych), a Prettier obsłużyć styl; łącz je warstwami i zatrzymuj się przy pierwszej porażce.
Przejście parsera w lokalnym edytorze nie znaczy, że plik należy do CI; przejście CI też nie znaczy, że pola odpowiadają kontraktowi.
1. Rozdziel walidację składni od walidacji schematem
Określenie „walidacja JSON” jest używane zbyt szeroko, dlatego zespoły często biorą jedno narzędzie do rozwiązania dwóch różnych problemów:
| Warstwa | Odpowiadane pytanie | Typowe narzędzia | Co oznacza porażka |
|---|---|---|---|
| Składnia | Czy ten plik jest poprawnym JSON-em? | jq empty, jsonlint, python -m json.tool |
Nawiasy, przecinki, cudzysłowy lub kodowanie są zepsute |
| Schemat | Czy pola, typy i wymagane właściwości pasują do kontraktu? | ajv-cli, check-jsonschema |
Plik się parsuje, ale struktura biznesowa jest zła |
| Styl | Czy wcięcia i kolejność kluczy są spójne? | Prettier, jq -S |
Formatowanie jest niespójne, a diffy hałaśliwe |
Te warstwy nie mogą się wzajemnie zastępować. Plik, który przechodzi składnię, nadal może nie przejść schematu: {"price": "12.5"} jest poprawnym JSON-em, ale jeśli schemat wymaga price jako liczby, nie jest akceptowalny. I odwrotnie: niezależnie od kompletności schematu plik bez zamkniętego nawiasu nigdy się z nim nie skompiluje.
Dlatego potok powinien najpierw uruchamiać składnię, potem strukturę, a na końcu styl. Zatrzymaj się, gdy dowolna warstwa zawiedzie, inaczej błąd parsowania przebiera się za błąd pola.
2. Warstwa składni: jq empty jest opcją o minimalnych zależnościach
Jeśli jq jest już w obrazie CI, walidacja składni kosztuje niemal nic:
jq empty data.json
jq empty nie produkuje żadnego wyjścia; tylko parsuje plik. Kończy się kodem 0 przy sukcesie oraz niezerowym kodem i informacją o wierszu/kolumnie przy porażce. To czyni go lżejszym niż ściąganie pełnego drzewa zależności Node i dobrym pierwszym punktem kontrolnym.
Przy walidacji wsadowej plików JSON w repozytorium wyklucz node_modules, wyjście budowania i cache zewnętrzne:
find . -name '*.json' \
-not -path './node_modules/*' \
-not -path './dist/*' \
-print0 | xargs -0 -n1 jq empty
-print0 i xargs -0 obsługują spacje w nazwach plików, a -n1 zapewnia jeden plik per wywołanie, dzięki czemu komunikat błędu wskazuje winowajcę. Nie przekazuj setek plików do jednego procesu; komunikaty błędów będą się wzajemnie nadpisywać.
jsonlint to inny częsty wybór, zwykle wywoływany jako npx jsonlint -q data.json, gdzie -q wypisuje tylko błędy. Podaje numery wiersza i kolumny, ale jest utrzymywany mniej aktywnie niż jq, a różne wersje różnie tolerują BOM i komentarze. Traktuj go jako czytelniejszy sprawdzacz składni, a nie walidator schematu.
3. Warstwa schematu: podstawy ajv-cli validate
Ajv jest najszerzej używanym walidatorem schematu w ekosystemie JavaScript, a ajv-cli udostępnia go jako narzędzie wiersza poleceń:
ajv validate -s schema.json -d data.json
-s wskazuje plik schematu, a -d walidowane dane. Przy porażce proces kończy się niezerowym kodem, więc CI zawodzi natychmiast. Prawdziwe projekty zwykle dodają dwie flagi:
npx ajv-cli validate \
-s schemas/order.json \
-d 'fixtures/orders/*.json' \
--strict=false \
--errors=text
--strict=false pomaga przy starszych schematach: tryb ścisły traktuje nieznane słowa kluczowe i podejrzane deklaracje required jako błędy, czemu wiele odziedziczonych schematów nie sprosta. --errors=text produkuje wyjście czytelne dla ludzi, a domyślna struktura JSON jest lepsza dla maszyn. Wybierz według tego, kto konsumuje wynik.
Wersja draftu schematu też musi się zgadzać. Główne wersje Ajv domyślnie wspierają różne drafty, a ajv-cli potrafi wybrać jeden przez --spec=draft7, --spec=draft2019 lub --spec=draft2020. Jeśli schemat deklaruje $schema: "https://json-schema.org/draft/2020-12/schema", a CLI kompiluje go jako draft-07, niektóre słowa kluczowe są po cichu ignorowane. Więcej o tworzeniu schematów i różnicach wersji znajdziesz w Pierwsze kroki z JSON Schema.
Walidacja formatów to kolejne częste wymaganie. Słowo kluczowe format domyślnie jest tylko adnotacją; aby email, date-time i uri faktycznie coś wymuszały, załaduj ajv-formats:
npx ajv-cli validate -c ajv-formats -s schema.json -d data.json
Ten etap łatwo przeoczyć, a skutkiem jest pole e-mail akceptujące not-an-email. Przed wdrożeniem podaj potokowi jeden celowo niepoprawny rekord; to dowodzi, że reguła jest aktywna, znacznie lepiej niż dziesięciokrotne czytanie dokumentacji.
check-jsonschema: wiele formatów i przyjaźniejsze błędy
Jeśli zespół obok JSON-a obsługuje YAML, TOML lub konfigurację GitHub Actions, check-jsonschema jest wygodniejszy:
check-jsonschema --schemafile schemas/config.json config/app.json
check-jsonschema --schemafile schemas/pipeline.json .github/workflows/ci.yml
Jego praktyczne zalety:
- Natywne wsparcie YAML, które pasuje do plików „o kształcie JSON, ale pisanych w YAML”, takich jak manifesty Kubernetes i konfiguracja CI.
- Błędy zawierają ścieżkę pliku, numer wiersza i ścieżkę pola, co obniża koszt lokalizacji.
- Zapewnia hak pre-commit, więc integracja jest tania.
- Potrafi pobierać popularne schematy z SchemaStore, ale akceptuje też pliki lokalne, unikając zależności sieciowej.
W zespołach wielojęzycznych Python jest często już obecny, więc instalacja i aktualizacja check-jsonschema jest lżejsza niż utrzymywanie łańcucha narzędzi Node. Pozwól jq obsłużyć składnię, a temu narzędziu strukturę; kombinacja ma bardzo niski narzut utrzymania.
4. Kody wyjścia to jedyny wiarygodny sygnał dla CI
Niezależnie od tego, jak dopracowane jest wyjście, CI czyta tylko kody wyjścia. Trzy pułapki są częste:
| Objaw | Przyczyna | Poprawka |
|---|---|---|
| Błędy istnieją, ale CI jest zielone | Polecenie kończy się ` | |
| Brak błędów, ale CI jest czerwone | diff traktuje „znaleziono różnice” jako porażkę |
Rozróżnij „różnicę” od „błędu wykonania” |
| Wyjście błędów jest obcięte | Równoległe wyjście wielu plików się nadpisuje | Uruchamiaj szeregowo albo wypisuj per plik |
Pamiętaj semantykę kodów wyjścia jq: normalne parsowanie zwraca 0; z -e wynik wyrażenia false lub null zwraca 1; błędy użycia i brakujące pliki zwracają 2; błędy kompilacji skryptu zwracają 3. Narzędzia schematów zwykle używają 0 dla sukcesu, 1 dla porażki walidacji i 2 dla błędów narzędzia lub argumentów. Pisząc wrapper, nie zwijaj każdego niezerowego kodu w „dane są złe”, inaczej awaria środowiska zostanie zdiagnozowana jako problem treści.
5. pre-commit: złap problemy przed commitem
Wartością pre-commit nie jest „jeszcze jedna kontrola”, ale danie deweloperom informacji zwrotnej w minutę, a nie po dziesięciominutowym uruchomieniu CI. Minimalna konfiguracja wygląda tak:
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$
Dwa szczegóły mają znaczenie:
filespowinno pasować tylko do katalogów wymagających walidacji. Nie uruchamiaj haków nanode_modulesani plikach generowanych.- Hak lokalny zakłada, że
jqjest zainstalowany na maszynie dewelopera. Udokumentuj ten wymóg albo przejdź na hak konteneryzowany.
Jeśli projekt używa już Husky i lint-staged, połącz je bezpośrednio:
{
"lint-staged": {
"configs/*.json": ["jq empty", "prettier --check"]
}
}
Pamiętaj, że lint-staged przekazuje listę plików w poczekalni, więc polecenie musi akceptować wiele ścieżek. Zarówno jq empty, jak i prettier --check to robią. Nie pozwól, by hak skanował całe repozytorium; commity staną się zauważalnie wolniejsze.
6. GitHub Actions: potok, który możesz skopiować
Poniższy workflow wykonuje walidację składni, walidację schematem i zachowuje raport przy porażce:
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
Filtr paths zapobiega wyzwalaniu walidacji przez zmiany wyłącznie dokumentacyjne. npx --yes pasuje do repozytoriów bez przypiętych zależności; projekty produkcyjne powinny dodać ajv-cli do devDependencies i przypiąć wersję. Bez lockfile’u wydanie upstream zmieniające domyślny draft lub tryb ścisły może zmienić potok na czerwony z dnia na dzień.
7. Lokalizacja błędów: od numerów wierszy do ścieżek pól
Połowa wartości walidatora to wykrywanie; druga połowa to lokalizacja. Naucz się czytać trzy rodzaje wyjścia:
- Błędy składni:
jqpodaje wiersz i kolumnę, często z fragmentem w pobliżu. Najpierw popraw nawiasy i przecinki; nie podejrzewaj od razu kodowania. - Błędy schematu:
instancePathAjv wskazuje położenie danych (na przykład/items/0/price),schemaPathwskazuje regułę (na przykład#/properties/items/items/properties/price/type), akeywordnazywa naruszone ograniczenie. - Błędy formatu: gdy kontrola
formatzawiedzie, komunikat często mówi tylko „must match format "email"”. Połącz go zinstancePath, aby zidentyfikować pole.
Wklej instancePath do notatki ze śledztwa; jest znacznie bardziej użyteczny niż ściana JSON-a. W tablicach indeks liczbowy mówi też, który rekord zawiódł. Jeśli klucz biznesowy jest bardziej intuicyjny, dopisz później wiersz mapowania, taki jak items[0].id = ORD-042.
Typowe błędy schematu, takie jak nadużywanie additionalProperties: false czy pisanie gałęzi oneOf, które faktycznie się nakładają, opisano w Typowe pułapki generowania JSON Schema. Błędna reguła walidacji jest niebezpieczniejsza niż brak walidacji, bo tworzy fałszywe poczucie bezpieczeństwa.
8. Tabela doboru narzędzi
| Potrzeba | Zalecane narzędzie | Dlaczego | Na co uważać |
|---|---|---|---|
| Tylko składnia | jq empty |
Zero zależności, szybkie, jasne kody wyjścia | Spacje w nazwach plików przy uruchomieniach wsadowych |
| Przyjaźniejsze błędy wiersza/kolumny | jsonlint |
Czytelne wyjście | Aktywność utrzymania jest ograniczona |
| Walidacja JSON Schema | ajv-cli |
Dojrzały ekosystem, strukturalne błędy | Zgadnij wersje draftu i załaduj wsparcie format |
| Repozytorium mieszane JSON i YAML | check-jsonschema |
Wiele formatów, przyjazny pre-commitowi | Źródło schematu i dostęp sieciowy |
| Szybka informacja zwrotna przed commitem | pre-commit / lint-staged | Przetwarza tylko pliki w poczekalni | Haki muszą akceptować wiele ścieżek |
| Spójne formatowanie | Prettier | Szeroko przyjęty | Użyj --check, gdy nie chcesz przepisywać plików |
Jedna końcowa rekomendacja: zweryfikuj, że potok faktycznie zawodzi, podając mu najpierw jeden celowo niepoprawny rekord. Największym ryzykiem nie jest skomplikowana konfiguracja, ale konfiguracja, która nigdy nie działa. Traktowanie „potrafi zawieść” jako kryterium akceptacji jest bardziej niezawodne niż traktowanie „przechodzi” jako takiego. Szerszy łańcuch narzędzi wiersza poleceń opisano w Edytory, wiersz poleceń i automatyzacja.
FAQ
Czy używać jq empty czy jsonlint?
W CI preferuj jq empty: zero zależności, szybki start i jasna semantyka kodów wyjścia. Użyj jsonlint, gdy potrzebujesz przyjaźniejszych komunikatów o wierszu/kolumnie albo prowadzisz śledztwo lokalnie. Oba wykonują tylko walidację składni; żaden nie sprawdza struktury pól.
Dlaczego ajv zgłasza nieznane słowo kluczowe?
Najpierw potwierdź, że draft $schema schematu zgadza się z wersją używaną przez CLI. Wiele błędów „nieznanego słowa kluczowego” to słowa nowego draftu napotkane przez starszy kompilator. Jeśli wersje się zgadzają, sprawdź literówkę. --strict=false może utrzymać działanie odziedziczonych schematów, ale nie polegaj na nim długoterminowo, by ukrywać prawdziwe problemy.
Walidacja schematem przechodzi, ale API nadal zawodzi. Dlaczego?
Schemat waliduje strukturę, nie semantykę biznesową. Na przykład status może być łańcuchem z legalnym wyliczeniem, podczas gdy maszyna stanów zabrania przejścia bezpośrednio z paid do refunded. Takie reguły należą do kodu lub ograniczeń bazy danych, a nie do JSON Schema.
Jak szybko zlokalizować zawodzące pole?
Odczytaj instancePath lub numer wiersza z wyjścia narzędzia. instancePath Ajv to JSON Pointer wskazujący bezpośrednio dane; check-jsonschema podaje plik i wiersz. Zapisz ścieżkę i zmapuj ją do klucza biznesowego; lokalizacja problemu zwykle zajmuje minuty.
Czy pre-commit powinien uruchamiać pełną walidację?
Nie. pre-commit powinien przetwarzać tylko pliki w poczekalni, aby commity pozostały szybkie. Pełne skany należą do CI lub zadań harmonogramu. Podział jest prosty: uruchomienia lokalne stawiają na szybkość, CI na kompletność.