json-validator.de

Ratgeber · JSON Schema 2020-12

JSON-Validation in CI/CD: Schema-Checks vor jedem Deploy

Pre-Commit: husky + lint-staged + ajv validate. Build-Step: ajv-cli compile. Vitest: Schema-Tests pro Endpoint, fail-fast bei Drift.

Foto von Mateusz Viola

Von Mateusz Viola

Betreiber & redaktionelle Verantwortung json-validator.de

Drei Validation-Stufen im Build-Prozess

Schema-Validation in CI/CD hat drei sinnvolle Stufen:

StufeWas validiert wirdTool
1. Schema-Selbst-ValidationSind die *.schema.json Files gültiges JSON Schema?ajv-cli compile
2. Fixture-ValidationPassen Test-Fixtures zu den Schemas?Vitest + Ajv
3. Contract-TestsLiefern Endpoints Schema-konforme Responses?Supertest + Ajv

Stufe 1: Schemas selbst sind valides JSON Schema

Klassischer Lookup-Fehler: "required": "name" statt "required": ["name"]. Wird beim Build sofort gefangen mit ajv-cli:

# package.json
"scripts": {
  "validate:schemas": "ajv compile --strict=true --spec=draft2020 -s 'src/schemas/**/*.schema.json'"
}

Mit --strict=true werden auch typische Anti-Patterns gemeldet: unbekannte Keywords (Tippfehler), kombinierte type+enum (redundant), etc.

Stufe 2: Test-Fixtures gegen Schemas

Wenn dein Code Test-Fixtures hat (JSON-Dateien mit Sample-Payloads), sollten die zum Schema passen:

// vitest test
import { describe, it, expect } from 'vitest';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import userSchema from '../schemas/user.schema.json';
import userFixture from '../fixtures/users/valid-user.json';

const ajv = new Ajv();
addFormats(ajv);
const validate = ajv.compile(userSchema);

describe('user fixture', () => {
  it('matches the schema', () => {
    const ok = validate(userFixture);
    expect(ok, JSON.stringify(validate.errors)).toBe(true);
  });
});

Bei Schema-Änderungen: Tests werden rot, du siehst sofort welche Fixtures angepasst werden müssen.

Stufe 3: Response-Validation als Contract-Test

Endpoint-Tests, die das Response-Body gegen das Response-Schema prüfen:

import { describe, it, expect } from 'vitest';
import request from 'supertest';
import { app } from '../app';
import responseSchema from '../schemas/users-get.response.schema.json';

const validate = ajv.compile(responseSchema);

describe('GET /users/:id', () => {
  it('returns a user matching the schema', async () => {
    const res = await request(app).get('/users/123');
    expect(res.status).toBe(200);
    expect(validate(res.body), JSON.stringify(validate.errors)).toBe(true);
  });
});

GitLab-CI-Beispiel

# .gitlab-ci.yml
stages:
  - validate
  - test
  - build

validate:schemas:
  stage: validate
  image: node:22-alpine
  script:
    - npm ci
    - npm run validate:schemas
  rules:
    - changes:
        - 'src/schemas/**/*.schema.json'

test:contracts:
  stage: test
  image: node:22-alpine
  script:
    - npm ci
    - npm run test
  needs: [validate:schemas]

Pre-Commit-Hook für lokale Validation

Mit husky + lint-staged:

# .lintstagedrc.json
{
  "src/schemas/**/*.schema.json": [
    "ajv compile --strict=true -s"
  ],
  "src/fixtures/**/*.json": [
    "node scripts/validate-fixture.mjs"
  ]
}

Vorteil: Schema-Fehler werden sofort beim Commit gefangen, nicht erst im CI.

OpenAPI-Linting parallel

Wenn du eine OpenAPI-Spec hast: Redocly CLI im selben Build-Step:

npx @redocly/cli lint openapi.yaml
npx @redocly/cli stats openapi.yaml

Typische CI-Fehler und Fix

FehlerFix
"strict mode: unknown keyword 'example'"example ist OpenAPI-spezifisch - in pure JSON Schema raus oder ignorieren via strict: false
"can't resolve reference $ref"loadSchema-Callback in Ajv konfigurieren, oder relative Pfade fix
"format 'date-time' validation failed"addFormats(ajv) vergessen - vor ajv.compile() aufrufen
"strict mode: required is not an array"required ist Array von Strings, nicht Object

Performance-Tipp: pre-compile zur Deploy-Zeit

Ajv kann Schemas vor-kompilieren und als JS-Modul exportieren:

npx ajv-cli compile -s schemas/user.schema.json -o dist/user-validate.js

Das fertige Modul lädst du dann zur Laufzeit - keine Compile-Zeit mehr beim App-Start, deutlich kleinere Bundle, kein ajv-Runtime nötig.

Mehr zum Thema