A single misplaced comma in a config.json file or a missing required property in an application configuration shouldn’t take down your deployment. Yet, because JSON is frequently edited by humans but consumed by strict automated systems, malformed payloads are a leading cause of deployment failures.

The solution is to shift validation left. By integrating JSON checks directly into your Continuous Integration and Continuous Deployment (CI/CD) pipelines, you can catch syntax errors and schema violations at the pull request stage — long before they reach staging or production. Every command below was actually run, with the real output and exit code shown.

JSON Validation in CI/CD pipelines

Automating JSON syntax and schema checks in a CI/CD pipeline

Tested: A Failing Pipeline, Step by Step

We set up a small repo with two config files: a good one and a broken one. The broken one has a single trailing comma — the most common JSON mistake in config files:

{
  "app": "kingstools",
  "port": "8080",
  "debug": true,
}

Here is what each layer of the pipeline does with it, run for real.

Level 1 run: jq catches the syntax error

$ jq . config.json
jq: parse error: Expected another key-value pair at line 5, column 1
jq exit code: 5

jq fails with exit code 5 and points at line 5, column 1 — the closing brace, where the trailing comma left it expecting another key. The same command on the good file exits 0 and prints the parsed JSON. In a pipeline, a non-zero exit is all it takes: the step fails, the job stops, and nothing deploys.

Level 2 run: ajv catches the wrong type that jq can’t see

Fix the comma but leave "port" as a string, and jq passes — the file is valid JSON. The schema check is what catches it. We validated against a draft-07 schema requiring port to be an integer:

$ npx ajv-cli validate -s schema.json -d config-good.json
config-good.json valid
ajv exit code: 0

$ npx ajv-cli validate -s schema.json -d config-bad-schema.json
config-bad-schema.json invalid
[
  {
    instancePath: '/port',
    schemaPath: '#/properties/port/type',
    keyword: 'type',
    params: { type: 'integer' },
    message: 'must be integer'
  }
]
ajv exit code: 1

The error names the exact field (/port), the exact rule (type: integer), and the exact failure (must be integer). That is the whole value of schema validation in a pipeline: the failure message tells the developer what to fix, not just that something is broken.

One gotcha we hit while running this: ajv-cli (v5) only knows draft-07 schemas. When we first tried a draft 2020-12 $schema, it refused to run at all — schema schema.json is invalid / error: no schema with key or ref "https://json-schema.org/draft/2020-12/schema" — and every file failed, valid or not. If your ajv run fails on every file at once, check your $schema line before anything else. The schema in this post already uses draft-07, which is why it works.

The full loop, run for real

The post’s find-loop script, run against a directory containing the broken file:

$ find configs -name "*.json" -print0 | while IFS= read -r -d '' file; do
  jq -e . "$file" >/dev/null || {
    echo "Invalid JSON: $file"
    exit 1
  }
done
jq: parse error: Expected another key-value pair at line 5, column 1
Invalid JSON: configs/config.json
loop exit code: 1

And the same loop against good files only: exit code 0, no output. That contrast is the point — in CI, silence means green and one line naming the file means red.

What the failed GitHub Actions run looks like

We ran the workflow’s steps in the same order, with the same commands and exit codes, so this is what the log shows when the bad config reaches the pipeline. (These are the local runs above, in workflow order — the step names are the workflow’s; the outputs and exit codes are the real ones.)

Run Check JSON Syntax with jq
  find . -name "*.json" -print0 | while IFS= read -r -d '' file; do
    jq -e . "$file" >/dev/null || {
      echo "Invalid JSON: $file"
      exit 1
    }
  done
  jq: parse error: Expected another key-value pair at line 5, column 1
  Invalid JSON: configs/config.json
  (step exits 1)

GitHub marks that step failed and stops the job there, so the “Setup Node.js”, “Install AJV CLI”, and “Validate Configs against Schema” steps never run.

That last part matters for how you structure the workflow: because the syntax step fails first, the schema step never runs. If a pull request has both a syntax error and a schema error, the developer only sees the syntax one, fixes it, pushes again, and then sees the schema one. That is normal CI behaviour, not a bug — but it means a bad config can take two pipeline runs to fully diagnose. If you want both reports in one run, put the syntax and schema checks in separate jobs that run in parallel instead of sequential steps in one job.

The Crucial Difference: Syntax vs. Schema

Before building automated workflows, it is important to understand that validating JSON happens in two distinct stages. A file can pass the first stage but fail the second — our runs above show exactly that: the port-as-string file passes jq and fails ajv.

  • Syntax validation asks: “Is this valid JSON?” It ensures there are no trailing commas, unescaped quotes, or missing brackets.
  • Schema validation asks: “Does this valid JSON have the structure and values my application expects?” It ensures required keys are present and data types (like integers vs. strings) are correct.

For the full picture of what each layer can and can’t catch — including a side-by-side run of the same payload through a linter, a parser, and a schema validator — see our guide to JSON lint vs JSON validation.

What CI/CD Validation Catches

Here is exactly how these two layers protect your deployments:

ProblemSyntax CheckSchema CheckSeen in Our Runs
Missing commaYesNo—
Trailing commaYesNoYes — jq exit code 5, line 5 column 1
Invalid JSON stringYesNo—
Missing required propertyNoYes—
Wrong data type (e.g., string instead of int)NoYesYes — ajv: /port must be integer, exit code 1
Invalid enum valueNoYes—
Value below minimum thresholdNoYes—

Level 1: Basic Syntax Validation

The first line of defense is ensuring the file is structurally valid. The fastest way to check syntax in a Linux-based CI runner is using jq, a lightweight command-line JSON processor.

A robust script will find all JSON files in your repository and exit with a non-zero status code if any file is malformed, explicitly telling you which file failed:

find . -name "*.json" -print0 | while IFS= read -r -d '' file; do
  jq -e . "$file" >/dev/null || {
    echo "Invalid JSON: $file"
    exit 1
  }
done

We ran this exact script above: against a directory containing the broken config it printed Invalid JSON: configs/config.json and exited 1; against good files only, it exited 0 silently. Two details worth noting: the -e flag makes jq exit non-zero on invalid input (without it, jq still prints an error but the exit code handling gets murkier in a loop), and -print0 with read -d '' handles filenames with spaces correctly.

Level 2: JSON Schema Validation

JSON Schema is a vocabulary that allows you to annotate and validate JSON documents. Using a tool like ajv-cli (Another JSON Schema Validator), you can enforce exact data structures in your pipeline.

First, define a schema (e.g., config-schema.json):

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "environment": { "type": "string", "enum": ["dev", "staging", "prod"] },
    "timeout": { "type": "integer", "minimum": 1000 }
  },
  "required": ["environment", "timeout"]
}

Then, run ajv-cli to validate your configuration files against this schema:

npx ajv-cli validate -s config-schema.json -d "configs/*.json"

Our run above used the same command shape with a schema requiring port to be an integer: the good file printed config-good.json valid (exit 0), the port-as-string file printed the full error object with instancePath: '/port' and message: 'must be integer' (exit 1). Remember the draft-07 gotcha from the test section: ajv-cli v5 doesn’t recognise a draft 2020-12 $schema, so keep the schema on draft-07 unless you upgrade the validator.

Shift Left: Pre-Commit Checks

While CI pipelines are essential, they shouldn’t be the first place developers discover malformed JSON. The most efficient workflow follows a logical progression: Editor → Local Check → Pull Request (CI) → Deployment.

Run the same validation locally before pushing a commit. You can manually test a single file from your terminal:

jq -e . config.json

Even better, integrate tools like Husky and lint-staged to automatically run these checks on every git commit, preventing broken syntax from ever leaving the developer’s machine. The same idea applies to formatting: our guide to formatting JSON for Git shows how a pre-commit hook can also normalise formatting, so a config change doesn’t arrive in a pull request as a thousand-line diff.

Practical CI/CD Workflows

Here is how to implement these robust checks in popular CI/CD platforms.

GitHub Actions Workflow

Create a file at .github/workflows/json-validation.yml to automatically run syntax and schema checks every time a pull request is opened.

name: Validate JSON Files

on:
  pull_request:
    paths:
      - '**.json'

jobs:
  validate-json:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Check JSON Syntax with jq
        run: |
          find . -name "*.json" -print0 | while IFS= read -r -d '' file; do
            jq -e . "$file" >/dev/null || {
              echo "Invalid JSON: $file"
              exit 1
            }
          done

      - name: Setup Node.js for AJV
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install AJV CLI
        run: npm install -g ajv-cli

      - name: Validate Configs against Schema
        run: ajv validate -s schemas/config-schema.json -d "configs/*.json"

The failing run at the top of this post is this workflow with a broken config in it: the jq step fails with exit code 1, the job stops, and the schema steps never run. One practical improvement: npm install -g ajv-cli on every run is slow. Pin ajv-cli as a dev dependency in your repo (npm install --save-dev ajv-cli) and call npx ajv instead — the install step disappears and the run gets faster.

GitLab CI Pipeline

For GitLab, you can achieve the exact same workflow in your .gitlab-ci.yml file:

stages:
  - validate

json_syntax_check:
  stage: validate
  image: alpine:latest
  before_script:
    - apk add --no-cache jq bash
  script:
    - |
      find . -name "*.json" -print0 | while IFS= read -r -d '' file; do
        jq -e . "$file" >/dev/null || {
          echo "Invalid JSON: $file"
          exit 1
        }
      done
  only:
    changes:
      - "**/*.json"

json_schema_check:
  stage: validate
  image: node:20-alpine
  script:
    - npm install -g ajv-cli
    - ajv validate -s schemas/config-schema.json -d "configs/*.json"

Note the structural difference from the GitHub version: GitLab runs the syntax and schema checks as separate jobs in the same stage, so they run in parallel and both reports appear in one pipeline run — the “two runs to find two errors” problem from the GitHub section doesn’t apply here.

Handling CI/CD Validation Failures

When a pipeline fails because of a JSON error, developers need a fast way to find the exact syntax break or schema mismatch without waiting for another 5-minute CI run.

If your runner logs report an Invalid JSON error, do not try to manually scan large configuration files in your editor. Instead, copy the failing payload from the pull request and paste it directly into our JSON Formatter and Validator. The tool will instantly highlight the exact line and character causing the syntax break, allowing developers to push a rapid fix and get the build back to green. For a payload that’s valid JSON but fails the schema — the /port must be integer case from our runs — the validator confirms the syntax is fine, which tells you the problem is the data, not the formatting; from there, our guide to repairing broken JSON walks through fixing malformed payloads step by step.

Frequently Asked Questions

Why did my pipeline fail on a file that looks fine in my editor?

Because editors are forgiving and parsers aren’t. The usual culprit is a trailing comma or a missing quote that your editor’s syntax highlighting doesn’t flag as an error. In our test run, a single trailing comma after "debug": true made jq fail with exit code 5 at line 5, column 1. Run jq -e . config.json locally — it points at the exact line and column.

What’s the difference between a syntax failure and a schema failure in CI?

A syntax failure means the file isn’t JSON at all — jq can’t parse it, and the error names a line and column. A schema failure means the file parses fine but has the wrong shape — ajv reports the field, the rule, and the message (in our run: /port, type: integer, must be integer). The syntax step runs first and blocks the schema step, so fix syntax errors before chasing schema ones.

Why does ajv fail on every file, even valid ones?

Check your $schema line. ajv-cli v5 only recognises draft-07 schemas; we hit this ourselves — a draft 2020-12 $schema made it reject the schema itself with no schema with key or ref, and every file failed. Use http://json-schema.org/draft-07/schema# or upgrade the validator.

Should the syntax and schema checks be one job or two?

Two jobs (or two parallel steps) if you want both reports in a single run. In the GitHub workflow above they’re sequential steps in one job, so a syntax failure skips the schema step entirely — a pull request with both errors takes two pipeline runs to fully diagnose. GitLab’s version uses separate jobs in the same stage, so both run and both report at once.

How do I stop broken JSON from reaching CI at all?

Run the same checks as a pre-commit hook. jq -e . config.json before every commit, or Husky + lint-staged to run it automatically, catches the trailing comma on the developer’s machine instead of in the pipeline. Our formatting JSON for Git guide covers the hook setup, including normalising formatting so config diffs stay readable.

Conclusion

Manual code reviews are an unreliable way to catch JSON errors. By enforcing automated syntax and schema validation in your CI/CD pipelines, you guarantee that malformed configurations are blocked at the pull request level. We proved it above with real runs: jq caught a trailing comma at line 5, column 1 (exit code 5); ajv caught a string where an integer belonged (/port must be integer, exit code 1); the find-loop named the failing file and exited 1; and the good files passed silently with exit code 0. That simple shift reduces deployment anxiety, prevents production downtime, and keeps your team focused on shipping features rather than hunting down missing commas.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *