JSON Toolkit

Валидация JSON в CI: конвейер с jsonlint, ajv и jq

Главное: Проверка синтаксиса доказывает, что файл разбирается; проверка схемы доказывает, что поля следуют контракту. В CI важны однозначные коды выхода, места ошибок и пути исправления.

Средний~10 мин чтенияОпубликовано 2026-10-31Обновлено 2026-10-31
Обложка статьи о валидации JSON в CI

Коротко: в CI пусть jq empty отвечает за синтаксис, ajv-cli — за структуру (check-jsonschema для команд с несколькими форматами), а Prettier — за стиль; связывайте их послойно и останавливайтесь на первом сбое.

То, что парсер в локальном редакторе прошёл, не значит, что файл место в CI; пройденный CI тоже не значит, что поля соответствуют контракту.

1. Разделите проверку синтаксиса и проверку схемы

Фраза «валидация JSON» используется слишком широко, поэтому команды часто берут один инструмент для решения двух разных задач:

Слой Какой вопрос отвечает Типичные инструменты Что означает сбой
Синтаксис Является ли этот файл допустимым JSON? jq empty, jsonlint, python -m json.tool Сломаны скобки, запятые, кавычки или кодировка
Схема Соответствуют ли поля, типы и обязательные свойства контракту? ajv-cli, check-jsonschema Файл разбирается, но бизнес-структура неверна
Стиль Согласованы ли отступы и порядок ключей? Prettier, jq -S Форматирование несогласовано, diff'ы шумные

Эти слои не могут заменять друг друга. Файл, прошедший синтаксис, всё ещё может провалить схему: {"price": "12.5"} — допустимый JSON, но если схема требует, чтобы price было числом, он не принимается. И наоборот: как бы полна ни была схема, файл без закрывающей скобки никогда против неё не скомпилируется.

Поэтому конвейер должен запускать сначала синтаксис, затем структуру и в конце стиль. Останавливайтесь, как только слой падает, иначе ошибка разбора маскируется под ошибку поля.

2. Слой синтаксиса: jq empty — вариант с минимумом зависимостей

Если jq уже есть в образе CI, проверка синтаксиса почти ничего не стоит:

jq empty data.json

jq empty ничего не выводит; он только разбирает файл. Завершается с 0 при успехе и ненулевым кодом плюс информацией о строке/столбце при неудаче. Это делает его легче, чем подключение полного дерева зависимостей Node, и хорошим первым барьером.

При пакетной валидации JSON-файлов в репозитории исключайте node_modules, результаты сборки и сторонние кеши:

find . -name '*.json' \
  -not -path './node_modules/*' \
  -not -path './dist/*' \
  -print0 | xargs -0 -n1 jq empty

-print0 и xargs -0 обрабатывают пробелы в именах файлов, а -n1 гарантирует один файл на вызов, чтобы сообщение об ошибке указывало на виновника. Не передавайте сотни файлов одному процессу; сообщения об ошибках будут перезаписывать друг друга.

jsonlint — другой частый выбор, обычно вызывается как npx jsonlint -q data.json, где -q печатает только ошибки. Он сообщает номера строк и столбцов, но поддерживается менее активно, чем jq, и разные версии по-разному терпят BOM и комментарии. Считайте его более читаемым проверщиком синтаксиса, а не валидатором схем.

3. Слой схемы: основы ajv-cli validate

Ajv — самый распространённый валидатор схем в экосистеме JavaScript, а ajv-cli предоставляет его как инструмент командной строки:

ajv validate -s schema.json -d data.json

-s указывает на файл схемы, -d — на проверяемые данные. При сбое процесс завершается ненулевым кодом, поэтому CI сразу падает. Реальные проекты обычно добавляют два флага:

npx ajv-cli validate \
  -s schemas/order.json \
  -d 'fixtures/orders/*.json' \
  --strict=false \
  --errors=text

--strict=false помогает со старыми схемами: строгий режим считает ошибками неизвестные ключевые слова и подозрительные объявления required, на чём падают многие легаси-схемы. --errors=text даёт человекочитаемый вывод, тогда как структура JSON по умолчанию лучше для машин. Выбирайте по тому, кто будет потреблять результат.

Версия черновика схемы тоже должна совпадать. Мажорные версии Ajv по умолчанию поддерживают разные черновики, и ajv-cli может выбрать один через --spec=draft7, --spec=draft2019 или --spec=draft2020. Если схема объявляет $schema: "https://json-schema.org/draft/2020-12/schema", а CLI компилирует её как draft-07, некоторые ключевые слова молча игнорируются. О написании схем и различиях версий читайте в статье Начало работы с JSON Schema.

Проверка форматов — ещё одно частое требование. Ключевое слово format по умолчанию только аннотация; чтобы email, date-time и uri действительно что-то проверяли, загрузите ajv-formats:

npx ajv-cli validate -c ajv-formats -s schema.json -d data.json

Этот шаг легко пропустить, и в результате поле email принимает not-an-email. Перед выпуском подайте конвейеру одну заведомо неверную запись; это доказывает, что правило активно, гораздо лучше, чем десятикратное чтение документации.

check-jsonschema: несколько форматов и более дружелюбные ошибки

Если команда работает не только с JSON, но и с YAML, TOML или конфигурацией GitHub Actions, check-jsonschema удобнее:

check-jsonschema --schemafile schemas/config.json config/app.json
check-jsonschema --schemafile schemas/pipeline.json .github/workflows/ci.yml

Его практические преимущества:

  1. Нативная поддержка YAML, что подходит файлам «формы JSON, но написанным в YAML», например манифестам Kubernetes и конфигурации CI.
  2. Ошибки включают путь к файлу, номер строки и путь к полю, что снижает затраты на локализацию.
  3. Предоставляется хук pre-commit, поэтому интеграция дешёвая.
  4. Он может брать общие схемы из SchemaStore, но также принимает локальные файлы, избегая сетевой зависимости.

В кросс-языковых командах Python часто уже присутствует, поэтому установка и обновление check-jsonschema легче поддержания цепочки Node. Пусть jq отвечает за синтаксис, а этот инструмент — за структуру; у такой комбинации очень низкие затраты на поддержку.

4. Коды выхода — единственный надёжный сигнал CI

Каким бы отшлифованным ни был вывод, CI читает только коды выхода. Часты три ловушки:

Симптом Причина Исправление
Ошибки есть, но CI зелёный Команда оканчивается на `
Ошибок нет, но CI красный diff считает «различия найдены» сбоем Различайте «различие» и «ошибка выполнения»
Вывод ошибок обрезан Параллельный вывод по нескольким файлам перезаписывает себя Запускайте последовательно или печатайте по файлам

Помните семантику кодов выхода jq: обычный разбор возвращает 0; с -e результат выражения false или null возвращает 1; ошибки использования и отсутствующие файлы возвращают 2; ошибки компиляции скрипта возвращают 3. Инструменты схем обычно используют 0 для успеха, 1 для провала валидации и 2 для ошибок инструмента или аргументов. Когда пишете обёртку, не схлопывайте каждый ненулевой код в «данные неверны», иначе сбой среды будет ошибочно принят за проблему содержимого.

5. pre-commit: поймать проблемы до коммита

Ценность pre-commit не в «ещё одной проверке», а в обратной связи разработчику через минуту, а не после десятиминутного прогона CI. Минимальная конфигурация выглядит так:

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$

Важны две детали:

  • files должны совпадать только с каталогами, которые нужно валидировать. Не запускайте хуки против node_modules или сгенерированных файлов.
  • Локальный хук предполагает, что jq установлен на машине разработчика. Задокументируйте это требование или перейдите на контейнеризованный хук.

Если проект уже использует Husky и lint-staged, объедините их напрямую:

{
  "lint-staged": {
    "configs/*.json": ["jq empty", "prettier --check"]
  }
}

Помните, что lint-staged передаёт список подготовленных файлов, поэтому команда должна принимать несколько путей. И jq empty, и prettier --check умеют это. Не позволяйте хуку сканировать весь репозиторий; коммиты заметно замедлятся.

6. GitHub Actions: конвейер, который можно скопировать

Рабочий процесс ниже выполняет проверку синтаксиса, проверку схемы и сохраняет отчёт при сбое:

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

Фильтр paths не даёт изменениям только документации запускать валидацию. npx --yes подходит репозиториям без закреплённых зависимостей; продакшен-проекты должны добавить ajv-cli в devDependencies и закрепить версию. Без lock-файла релиз вышестоящего пакета, меняющий черновик по умолчанию или строгий режим, может сделать конвейер красным за одну ночь.

7. Место ошибки: от номеров строк до путей полей

Половина ценности валидатора — обнаружение; другая половина — локализация. Научитесь читать три вида вывода:

  1. Синтаксические ошибки: jq сообщает строку и столбец, часто с фрагментом рядом. Сначала исправляйте скобки и запятые; не подозревайте кодировку сразу.
  2. Ошибки схемы: instancePath в Ajv указывает на место в данных (например, /items/0/price), schemaPath — на правило (например, #/properties/items/items/properties/price/type), а keyword называет нарушенное ограничение.
  3. Ошибки формата: когда проверка format падает, сообщение часто говорит только «must match format "email"». Сопоставьте его с instancePath, чтобы определить поле.

Вставьте instancePath в запись расследования; он гораздо полезнее стены JSON. Для массивов числовой индекс также говорит, какая запись провалилась. Если бизнес-ключ интуитивнее, добавьте позже строку сопоставления, например items[0].id = ORD-042.

Частые ошибки схем, такие как злоупотребление additionalProperties: false или написание веток oneOf, которые на самом деле пересекаются, разобраны в статье Частые ловушки генерации JSON Schema. Неверное правило валидации опаснее отсутствия валидации, потому что создаёт ложное чувство безопасности.

8. Таблица выбора инструмента

Потребность Рекомендуемый инструмент Почему За чем следить
Только синтаксис jq empty Ноль зависимостей, быстрый, ясные коды выхода Пробелы в именах файлов при пакетном запуске
Более дружелюбные ошибки строк/столбцов jsonlint Читаемый вывод Поддержка ограничена
Валидация JSON Schema ajv-cli Зрелая экосистема, структурированные ошибки Согласуйте версии черновика и загрузите поддержку format
Смешанный репозиторий JSON и YAML check-jsonschema Несколько форматов, дружелюбен к pre-commit Источник схем и сетевой доступ
Быстрая обратная связь до коммита pre-commit / lint-staged Обрабатывает только подготовленные файлы Хуки должны принимать несколько путей
Согласованное форматирование Prettier Широко принят Используйте --check, когда не хотите переписывать файлы

Последняя рекомендация: убедитесь, что конвейер действительно падает, подав ему одну заведомо неверную запись. Самый большой риск — не сложная конфигурация, а конфигурация, которая никогда не срабатывает. Считать «он может упасть» критерием приёмки надёжнее, чем считать таковым «он проходит». О более широкой цепочке командной строки читайте в статье Редакторы, командная строка и автоматизация.

Вопросы и ответы

Использовать jq empty или jsonlint?

В CI предпочитайте jq empty: ноль зависимостей, быстрый запуск и ясная семантика кодов выхода. jsonlint берите, когда нужны более дружелюбные сообщения о строке/столбце или вы расследуете локально. Оба выполняют только проверку синтаксиса; ни один не проверяет структуру полей.

Почему ajv сообщает о неизвестном ключевом слове?

Сначала подтвердите, что черновик $schema в схеме совпадает с версией, которую использует CLI. Многие ошибки «неизвестное ключевое слово» — это ключевые слова нового черновика, встреченные старым компилятором. Если версии совпадают, проверьте опечатку. --strict=false может сохранить работу легаси-схем, но не полагайтесь на него долго, чтобы скрывать реальные проблемы.

Валидация схемы проходит, но API всё равно падает. Почему?

Схема проверяет структуру, а не бизнес-семантику. Например, status может быть строкой с допустимым перечислением, тогда как конечный автомат запрещает прямой переход из paid в refunded. Такие правила относятся к коду или ограничениям базы данных, а не к JSON Schema.

Как быстро найти провалившееся поле?

Прочитайте instancePath или номер строки из вывода инструмента. instancePath в Ajv — это JSON Pointer, который указывает прямо на данные; check-jsonschema сообщает файл и строку. Запишите путь и сопоставьте его с бизнес-ключом; локализация проблемы обычно занимает минуты.

Должен ли pre-commit запускать полную валидацию?

Нет. pre-commit должен обрабатывать только подготовленные файлы, чтобы коммиты оставались быстрыми. Полные сканирования относятся к CI или плановым задачам. Разделение простое: локальные прогоны в пользу скорости, CI — в пользу полноты.