JSON Toolkit

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.

Średniozaawansowany~10 min czytaniaOpublikowano 2026-10-31Zaktualizowano 2026-10-31
Okładka artykułu o walidacji JSON w CI

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:

  1. Natywne wsparcie YAML, które pasuje do plików „o kształcie JSON, ale pisanych w YAML”, takich jak manifesty Kubernetes i konfiguracja CI.
  2. Błędy zawierają ścieżkę pliku, numer wiersza i ścieżkę pola, co obniża koszt lokalizacji.
  3. Zapewnia hak pre-commit, więc integracja jest tania.
  4. 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:

  • files powinno pasować tylko do katalogów wymagających walidacji. Nie uruchamiaj haków na node_modules ani plikach generowanych.
  • Hak lokalny zakłada, że jq jest 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:

  1. Błędy składni: jq podaje wiersz i kolumnę, często z fragmentem w pobliżu. Najpierw popraw nawiasy i przecinki; nie podejrzewaj od razu kodowania.
  2. Błędy schematu: instancePath Ajv wskazuje położenie danych (na przykład /items/0/price), schemaPath wskazuje regułę (na przykład #/properties/items/items/properties/price/type), a keyword nazywa naruszone ograniczenie.
  3. Błędy formatu: gdy kontrola format zawiedzie, komunikat często mówi tylko „must match format "email"”. Połącz go z instancePath, 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ść.