Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Integrating JSON Validation with Git Pre-Commit Hooks

Configuration files, translations, API response examples – JSON is everywhere in modern development. Keeping these files consistent and valid is crucial for project stability and developer sanity. An invalid JSON file can lead to runtime errors, deployment failures, or subtle bugs that are hard to track down.

Catching these errors as early as possible in the development lifecycle is key. One of the most effective ways to do this is by integrating JSON validation directly into your Git workflow using pre-commit hooks.

Why Git Pre-Commit Hooks?

Git hooks are scripts that Git executes before or after events like commit, push, and receive. Pre-commit hooks run *before* the commit process finishes. If a pre-commit script exits with a non-zero status, Git aborts the commit. This provides a perfect opportunity to run automated checks on your code (or data files) and prevent commits that fail those checks.

Using a pre-commit hook for JSON validation means:

  • Proactive Error Prevention: You catch validation errors before they even make it into your commit history or reach shared branches.
  • Consistent Codebase: Ensures that all JSON files across the project adhere to defined standards and syntax rules.
  • Faster Feedback Loop: Developers get immediate feedback if their JSON changes are invalid, without needing to wait for CI/CD pipelines.

Why Validate JSON? Syntax vs. Schema

JSON validation typically falls into two categories:

  1. Syntax Validation: This is the most basic check. It ensures that the file content follows the fundamental rules of the JSON format (correct use of braces {}, brackets [], commas ,, colons :, string quoting, etc.). An invalid JSON file is fundamentally unreadable by JSON parsers.
  2. Schema Validation: This goes beyond basic syntax. It checks if the JSON data conforms to a predefined structure or "schema". A schema specifies things like:
    • Which keys are required?
    • What data types should values have (string, number, boolean, array, object)?
    • What are the valid values for a specific key (e.g., from an enum list)?
    • How should arrays or objects be structured?
    Schema validation is crucial for complex configuration or data files where specific structure and types are expected.

Tools for the Job

You'll need a few tools:

  • JSON Validator: A command-line tool to check JSON syntax. Examples include jsonlint, jq (can be used for basic validation), or built-in options in some environments (like Node.js JSON.parse).
  • JSON Schema Validator (Optional but recommended): A tool that validates a JSON file against a JSON Schema. Examples include ajv-cli (for JavaScript/Node.js), json-schema-validator (Python), etc.
  • Git Hook Manager (Recommended): While you can write shell scripts directly in the .git/hooks/pre-commit file, managing hooks across a team is easier with tools like husky (for Node.js projects), pre-commit (Python-based, general purpose), or lefthook. These tools make hooks version-controlled and shareable.
  • Staged File Filter (with Hook Manager): Tools like lint-staged (often used with Husky) allow you to run commands only on the files that are currently staged for commit, which is much more efficient than checking every file in the repository.

Method 1: Simple Syntax Validation Hook

Let's start with a basic hook script that checks only the syntax of staged JSON files. We'll use jsonlint as an example validator.

Installation

Install jsonlint globally or as a dev dependency in your project:

npm install -g jsonlint

or

npm install --save-dev jsonlint

Manual Hook Script (.git/hooks/pre-commit)

Navigate to your project's .git/hooks/ directory. Create a file named pre-commit (if it doesn't exist) and make it executable (chmod +x .git/hooks/pre-commit). Add the following script:

#!/bin/sh

# Get a list of staged JSON files
JSON_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\\.json')

# Check if any JSON files are staged
if [ -z "$JSON_FILES" ]; then
  echo "No JSON files staged. Skipping JSON syntax validation."
  exit 0 # Exit successfully if no JSON files
fi

echo "Running JSON syntax validation on staged files..."

# Loop through each staged JSON file and validate
for file in $JSON_FILES; do
  if jsonlint "$file"; then
    echo "  ${file}: PASSED"
  else
    echo "  ${file}: FAILED syntax validation!"
    echo "  Commit aborted."
    exit 1 # Exit with error if validation fails
  fi
done

echo "All staged JSON files passed syntax validation."
exit 0 # Exit successfully if all files pass

Now, whenever you try to commit, this script will run, check staged .json files with jsonlint, and block the commit if any file has a syntax error.

Method 2: Adding JSON Schema Validation

To enforce structure and data types, you need schema validation. JSON Schema is a powerful standard for this. Let's use ajv-cli as an example.

Installation

Install ajv-cli and ajv (as a peer dependency):

npm install --save-dev ajv ajv-cli

Define Your Schema

Create a JSON Schema file (e.g., config.schema.json) that describes the structure of your data:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Configuration Settings",
  "description": "Schema for application configuration file",
  "type": "object",
  "properties": {
    "apiEndpoint": {
      "type": "string",
      "format": "url"
    },
    "timeoutSeconds": {
      "type": "integer",
      "minimum": 1,
      "maximum": 300
    },
    "features": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": ["featureA", "featureB", "featureC"]
      },
      "uniqueItems": true
    },
    "enableCaching": {
      "type": "boolean"
    }
  },
  "required": [
    "apiEndpoint",
    "timeoutSeconds"
  ],
  "additionalProperties": false
}

Update the Hook Script

Modify the .git/hooks/pre-commit script to also run schema validation after syntax check. You'll need to map which JSON files should be validated against which schema.

#!/bin/sh

# Mapping of JSON files to their schema files
# IMPORTANT: Define your specific file-to-schema mappings here!
declare -A SCHEMA_MAP
SCHEMA_MAP["path/to/your/config.json"]="path/to/your/config.schema.json"
# Add other mappings as needed:
# SCHEMA_MAP["path/to/another/data.json"]="path/to/another/data.schema.json"

# Get a list of staged JSON files that are in the schema map
JSON_FILES_TO_VALIDATE=""
for file in $(git diff --cached --name-only --diff-filter=ACM | grep '\\.json'); do
    if [[ -n "${SCHEMA_MAP["$file"]}" ]]; then
        JSON_FILES_TO_VALIDATE="$JSON_FILES_TO_VALIDATE $file"
    fi
done

# Check if any relevant JSON files are staged
if [ -z "$JSON_FILES_TO_VALIDATE" ]; then
  echo "No relevant JSON files staged for schema validation. Skipping validation."
  exit 0 # Exit successfully if no relevant JSON files
fi

echo "Running JSON schema validation on staged files..."

# Loop through each relevant staged JSON file and validate against its schema
VALIDATION_FAILED=0
for file in $JSON_FILES_TO_VALIDATE; do
  schema_file="${SCHEMA_MAP["$file"]}"

  if [ ! -f "$schema_file" ]; then
    echo "ERROR: Schema file not found for ${file}: ${schema_file}"
    VALIDATION_FAILED=1
    continue # Continue to check other files
  fi

  # Run ajv-cli validation
  # Assumes ajv-cli is available in your PATH (e.g., installed globally or in node_modules/.bin)
  if npx ajv-cli validate -s "$schema_file" -d "$file"; then
    echo "  ${file}: PASSED schema validation."
  else
    echo "  ${file}: FAILED schema validation!"
    VALIDATION_FAILED=1 # Mark failure but continue to check other files
  fi
done

if [ "$VALIDATION_FAILED" -eq 1 ]; then
  echo "JSON schema validation failed for one or more files."
  echo "Commit aborted."
  exit 1
else
  echo "All relevant staged JSON files passed schema validation."
  exit 0
fi

Note: This script assumes ajv-cli is available. Using npx is a common way to run tools installed as dev dependencies in Node.js projects. You must update the SCHEMA_MAP variable to list the JSON files in your project and their corresponding schema files.

Method 3: Using Hook Managers (Husky + Lint-Staged)

Directly modifying .git/hooks/ is problematic: hooks are local to your repository clone, not version-controlled by default, and hard to keep consistent across a team. Hook managers solve this. husky and lint-staged are a popular combination for Node.js projects.

Installation

Install husky and lint-staged as dev dependencies:

npm install --save-dev husky lint-staged

Initialize Husky (this sets up the Git hooks pointing to Husky):

npx husky init

This creates a .husky directory and a pre-commit file inside it, managed by Husky.

Configuration (package.json)

Add a lint-staged configuration to your package.json. This specifies commands to run on staged files matching certain glob patterns.

{
  "name": "your-project",
  "version": "1.0.0",
  "devDependencies": {
    "husky": "^8.0.0",
    "lint-staged": "^13.0.0",
    "jsonlint": "^1.6.0",
    "ajv": "^8.0.0",
    "ajv-cli": "^5.0.0"
  },
  "scripts": {
    "prepare": "husky install"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.json": [
      "jsonlint --compact", // Basic syntax check
      // Add schema validation for specific files
      // NOTE: Schema validation with lint-staged requires careful scripting
      // as ajv-cli needs the schema path and the file path.
      // You might need a small helper script or map here.
      // Example (conceptual - requires specific implementation):
      // "node scripts/validate-json-schema.js --schema-map config.json:config.schema.json"
    ],
    "path/to/your/config.json": [
       "npx ajv-cli validate -s path/to/your/config.schema.json -d" // Validate this specific file against its schema
    ]
    // Add more specific schema validation rules for other files
    // "path/to/another/*.json": [
    //    "node scripts/validate-dynamic-schema.js --schema-dir schemas/" // Example using a helper script to find schemas
    // ]
  }
}

In this setup:

  • "prepare": "husky install" ensures Husky is installed when dependencies are installed.
  • "husky": {"hooks": {"pre-commit": "lint-staged"}} tells Husky to run lint-staged on pre-commit.
  • "lint-staged" config maps file patterns (e.g., "*.json" or "path/to/your/config.json") to commands (e.g., "jsonlint --compact" or "npx ajv-cli..."). lint-staged passes the list of staged files matching the pattern to the command.

Integrating schema validation for multiple files with lint-staged and ajv-cli can be slightly complex if you have many files each requiring a different schema. You might need a small Node.js or shell script that lint-staged calls, which then iterates through the provided file list and runs ajv-cli with the correct schema for each file based on your mapping logic. The example shows validating a specific file directly.

Benefits of Using a Hook Manager

  • Version Controlled: The hook configuration lives in your package.json (or dedicated config files), which is tracked by Git.
  • Shareable: All team members get the same hooks automatically when they clone the repository and install dependencies.
  • Targeted Checks: lint-staged ensures validation only runs on the files you've modified and staged, making it fast.
  • Easy Integration: Works well with other pre-commit checks like linters, formatters, and tests.

Tips and Considerations

  • Performance: Validation should be fast. Avoid complex or long-running checks in pre-commit hooks, as they can frustrate developers. Validating only staged files (via lint-staged or equivalent) is key for performance.
  • Error Messages: Ensure your validation tools provide clear error messages indicating *what* is wrong and *where*. This helps developers fix issues quickly.
  • Schema Management: Keep your JSON Schemas well-organized and version-controlled alongside your data files.
  • Multiple JSON Files: If your project has many different types of JSON files, each requiring a different schema, careful mapping in your hook script or lint-staged config is needed. A dedicated helper script can simplify this.
  • Skipping Hooks: Developers can bypass hooks using git commit --no-verify. While sometimes necessary, this should be discouraged for regular commits, especially on shared branches.

Conclusion

Integrating JSON validation into your Git pre-commit hooks is a straightforward yet powerful way to improve the quality and consistency of your codebase. Whether you opt for a simple syntax check with a bash script or a more robust setup with a hook manager like Husky and schema validation using tools like AJV, the principle is the same: catch errors early, automate checks, and ensure that only valid JSON makes it into your repository. This practice saves time, reduces bugs, and makes collaboration smoother.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool