Validating and formatting JSON in CI and code review

How to catch JSON syntax errors early, read common parser error messages, and use schemas and minification correctly in CI.

Published 2026-09-25

Why JSON breaks silently until it doesn't

JSON has a small, strict grammar defined in RFC 8259: no trailing commas, no comments, keys must be double-quoted strings, and numbers can't have leading zeros. None of that is enforced by most text editors, so a hand-edited JSON config file can look completely reasonable and still fail to parse — and because JSON is usually read at runtime (a server starting up, a build step loading a config), a syntax error often surfaces as a confusing crash far from the file that caused it, rather than at the point where the mistake was made.

Catching this at commit time or in CI — before the broken file merges — is cheaper than debugging a production startup failure. That's the whole case for validating JSON as a review step rather than trusting it by inspection.

Reading parser error messages

Different JSON parsers report the same three or four mistakes with different wording, but they're consistent within an ecosystem. Testing against a trailing comma ({"a": 1,}) and an unquoted key ({a: 1}):

Node.js (JSON.parse):

SyntaxError: Expected double-quoted property name in JSON at position 8 (line 1 column 9)
SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)

Python 3.13+ (json.loads; older versions report the trailing comma as "Expecting property name enclosed in double quotes"):

json.decoder.JSONDecodeError: Illegal trailing comma before end of object: line 1 column 8 (char 7)
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

Both point at line and column, but note that the reported position is where the parser noticed the problem, not always where the mistake visually is — a missing comma between two array elements is typically reported at the start of the second element, not at the end of the first, since that's the point where the parser expected a , or a closing bracket and found something else instead. When a JSON validator points at a location, check the token immediately before it too, not just the exact character.

The syntax errors that account for nearly all real-world JSON breakage:

  • Trailing comma after the last item in an object or array — allowed in JavaScript object literals, not in JSON.
  • Unquoted or single-quoted keys — JSON requires double quotes around every key, no exceptions.
  • Comments — // or /* */ are common in JS-adjacent config but are not valid JSON; if you need comments, you need a different format (or a JSON-with-comments variant your specific tool explicitly supports, like tsconfig.json's JSONC).
  • Unescaped control characters or backslashes inside strings — a literal newline inside a quoted string, or a single backslash not followed by a valid escape character, breaks parsing.
  • A trailing or leading value outside the top-level structure — extra content after the closing } or ], often from accidentally concatenating two JSON documents.

A JSON validator that reports the exact line and column turns this from a guessing exercise into a direct fix.

Formatting as a review aid, not just style

Consistent formatting matters for JSON in code review for a mechanical reason: diffs. A minified or inconsistently-indented JSON file turns a one-line config change into a diff that touches the whole file, or worse, hides the actual change among reformatting noise. Running JSON through a formatter with a fixed indent width (2 or 4 spaces, pick one per repo) before committing keeps diffs limited to the lines that actually changed, and keeps reviewers looking at the real edit instead of re-deriving it from a wall of reformatted braces.

Key sorting (many formatters offer it as an option) is worth using deliberately, not by default — sorting keys makes diffs cleaner when key order genuinely doesn't matter to the consumer, but breaks anything that depends on JSON preserving insertion order (some APIs and some human-readable exports do rely on order for readability, even though the JSON spec itself doesn't assign order any meaning).

Minification: for transport, not for source

Minifying JSON — stripping whitespace and line breaks — reduces payload size for anything sent over the network: API responses, build artifacts embedded in a bundle, config shipped to a browser. It should happen as a build or serve-time step, never to the version-controlled source file itself; a minified source file is unreadable in diffs and in code review, which defeats the entire point of keeping config in a text-based, reviewable format in the first place. The pattern that works well in CI: keep the formatted, indented version in source control, and minify only in the build step that produces the artifact actually shipped.

Schema validation: catching structure, not just syntax

A syntactically valid JSON document can still be structurally wrong — a required field missing, a string where a number was expected, an extra field that will be silently ignored downstream. JSON Schema exists for exactly this: a JSON document that describes the required shape of another JSON document, checked with a validator library rather than by eye.

{
  "type": "object",
  "required": ["service", "port"],
  "properties": {
    "service": { "type": "string" },
    "port": { "type": "integer", "minimum": 1, "maximum": 65535 }
  }
}

Wiring schema validation into CI — as a step that runs against every config file changed in a PR — catches the class of bug that syntax validation can't: a port number written as "8080" (a string) instead of 8080 (a number), a required field renamed in one file but not updated in the schema, or a typo'd key that the consuming program will just ignore rather than error on. Syntax validation (does this parse at all) and schema validation (does this parsed value have the right shape) are different checks and both are worth having — a file can pass one and fail the other.

What the CI step looks like

A parse check needs no extra dependencies if jq is installed (it is on GitHub's hosted Ubuntu runners). jq empty prints nothing and exits non-zero on invalid JSON:

for f in $(git ls-files '*.json'); do jq empty "$f" || exit 1; done

A format check with Prettier fails when a file is not already formatted:

npx prettier --check "**/*.json"

Schema validation of a config against a schema file, using the check-jsonschema package from PyPI:

pip install check-jsonschema
check-jsonschema --schemafile schema.json config.json

A minimal CI checklist

  1. Parse-check every changed .json file — a validator that fails the build on any syntax error, with line/column reported, catches the RFC 8259 violations above before merge.
  2. Format-check, don't silently reformat — fail CI if a file isn't already in the repo's canonical indent style, so diffs stay meaningful; run the formatter locally before committing, not as an auto-fix step in CI that masks the underlying habit.
  3. Schema-validate config files that have a defined shape — anything with required fields, typed values, or an enum of allowed values benefits from a schema check that syntax validation alone can't provide.
  4. Minify only at build/serve time, from the formatted source — never commit minified JSON as the file of record.

Tools mentioned in this guide