How to use json validation as a pre-commit quality check
- Step 1Drop the staged JSON file — Before
git add, drag the modified fixture, seed file, locale bundle, orpackage.jsononto the dropzone. Parsing runs locally; nothing uploads. Free tier accepts up to 2 MB, one file at a time. - Step 2Click Validate — The parser is strict RFC 8259 — the 'Strict mode' checkbox doesn't change that. Click Validate to run
JSON.parse. Green means it's safe to commit; red shows the first error and its position. - Step 3Fix and re-check — Jump to the reported character offset, fix the issue (often a trailing comma or a stray single quote), and re-drop the file. Repeat until green —
JSON.parsereports one error at a time, so a clean file means it parsed fully. - Step 4Eyeball the fixture shape — On success, the 'Parsed output' panel shows the value re-serialised. Confirm a fixture has the keys your test reads and that a missing comma didn't merge two records. (Preview truncates at 5,000 chars; Copy gives the full output.)
- Step 5Automate it with husky + lint-staged — Install:
npm i -D husky lint-staged, thennpx husky initand putnpx lint-stagedin.husky/pre-commit. In package.json:"lint-staged": { "*.json": "jq empty" }.jq emptyexits non-zero on invalid JSON, blocking the commit — the same RFC 8259 check this page runs. - Step 6Or combine validate + format with Prettier — If you also want consistent formatting, use
"lint-staged": { "*.json": ["prettier --write"] }. Prettier reparses (rejecting invalid JSON) and re-stages the formatted file. This validator stays useful for the quick manual look and for files Prettier is configured to ignore.
Mistakes that reach commits — and the strict-parser verdict
Verified against JSON.parse, the parser your pre-commit hook uses. 'Status' is whether the file would pass the hook.
| Mistake | Example | Verdict | Where it usually comes from |
|---|---|---|---|
| Trailing comma | {"a":1,} | Rejected | Editing package.json / a fixture by hand |
| Single quotes | {'a':1} | Rejected | Pasting a JS object literal into a .json file |
| Comment | {"a":1}// todo | Rejected | Leaving a note in a strict JSON file |
| Unquoted key | {a:1} | Rejected | Hand-typed fixture from memory |
| Two top-level values | {...}\n{...} | Rejected | Appending a second fixture to one file |
| Duplicate key | {"a":1,"a":2} | Valid (last wins, not flagged) | Merge conflict resolved badly |
| Clean fixture | {"id":1,"ok":true} | Valid | — |
Manual check vs automated hook vs Prettier
Three layers of pre-commit JSON safety. All use RFC 8259 parsing, so verdicts agree.
| Layer | What it does | When to use |
|---|---|---|
| This validator | One-file pass/fail + position, read-only | Quick check while editing a specific file |
lint-staged + jq empty | Blocks the commit on any invalid staged *.json | Team-wide enforcement, zero formatting changes |
lint-staged + prettier --write | Reformats + re-stages (rejects invalid) | When you also want consistent formatting |
| json-format-fixer | Repairs common mistakes, returns fixed JSON | When a file has many small errors to clean up |
JSON tool tier limits
Per-file size limits. Validation is single-file.
| Tier | Max file size | Batch files |
|---|---|---|
| Free | 2 MB | 1 |
| Pro | 100 MB | 10 |
| Developer | 5 GB | unlimited |
Cookbook
Real pre-commit JSON failures and how the manual check (and the matching hook) catch them. Values anonymised.
package.json trailing comma blocks npm install for everyone
ExampleA dependency was removed by hand, leaving a trailing comma after the last entry in dependencies. It commits fine, but the next person's npm install fails. The validator (and a jq empty hook) catches it pre-commit.
Input (package.json):
{
"dependencies": {
"react": "^18.0.0",
}
}
Validator output:
Invalid JSON
Expected double-quoted property name in JSON at position 46 (line 4 column 3)
Fix: remove the comma after the react entry.i18n locale file with a stray single quote
ExampleA translator's value contained an apostrophe and the editor 'helpfully' wrapped the whole value in single quotes. The locale file commits, then the app crashes on load in that language.
Input (fr.json):
{ "greeting": 'Bonjour' }
Validator output:
Invalid JSON
Expected property name or '}' in JSON at position 14
Fix: use double quotes -> "greeting": "Bonjour"
(json-format-fixer can convert single->double quotes automatically)Two fixtures appended into one file
ExampleA test fixture was duplicated by pasting a second object below the first. Each is valid, but JSON allows one top-level value, so the fixture loader throws.
Input (user.fixture.json):
{ "id": 1, "role": "admin" }
{ "id": 2, "role": "user" }
Validator output:
Invalid JSON
Unexpected non-whitespace character after JSON at position 28 (line 2 column 1)
Fix: wrap both in an array, or split into two fixture files.Merge conflict left a duplicate key (validator can't help)
ExampleResolving a merge conflict in a config fixture left the same key twice. This parses — V8 keeps the last value — so the validator passes it. This is the one class of bug syntax validation cannot catch.
Input (settings.fixture.json):
{ "timeout": 30, "timeout": 60 }
Validator output:
Valid JSON
Parsed output:
{ "timeout": 60 } <- the first 'timeout' silently vanished
Takeaway: review merge-conflict resolutions by eye; neither this
tool nor jq flags duplicate keys.Clean fixture ready to commit
ExampleAfter fixing, green plus a readable parsed shape confirms the fixture is both valid and structurally what the test expects.
Validator output:
Valid JSON (118 B)
Parsed output:
{
"id": 1,
"role": "admin",
"active": true
}
Safe to git add — and the lint-staged jq hook will pass it too.Errors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
Trailing comma in package.json / a fixture
Rejected{"a":1,} fails with Expected double-quoted property name. The most common reason a package.json or fixture breaks after a hand-edit. The validator points at the comma; json-format-fixer strips trailing commas automatically. A jq empty pre-commit hook blocks this for the whole team.
Duplicate keys (e.g. from a bad merge resolution)
By design — not flagged{"a":1,"a":2} parses and V8 keeps the last value. This validator does not detect duplicate keys — and neither does jq or node, so a jq-based hook won't either. The classic way this slips into a commit is a merge conflict resolved by keeping both sides. Review merge resolutions by eye.
Single quotes in a locale or fixture file
RejectedJSON requires double quotes. A value or key in single quotes (often pasted from JS) fails immediately. Convert with json-format-fixer, which has a single→double quote pass, or fix by hand before staging.
Comment left as a developer note
RejectedA // TODO or /* */ in a strict JSON file is rejected (Unexpected token /). If you need a note inside JSON, add a string field like "_note": "…" instead. Files that are genuinely JSONC (tsconfig) shouldn't go through a strict pre-commit check at all.
Two top-level values appended into one file
RejectedJSON permits one top-level value. Pasting a second fixture object below the first yields Unexpected non-whitespace character after JSON. Wrap the records in a [ ] array, or use NDJSON with a line-by-line loader (NDJSON is not single-document JSON).
Leading UTF-8 BOM on a committed file
SupportedA file saved by a Windows editor may begin with U+FEFF. The tool trims it before parsing, so it validates here. But some tools and diff tooling treat a BOM as a content change — configure your editor to save UTF-8 without BOM to keep diffs clean and avoid surprising stricter parsers.
Empty fixture file
RejectedAn empty .json fails with Unexpected end of JSON input. Usually means a generator wrote nothing or a file was created but not filled in. An empty JSON object is {}, an empty array is [] — pick the right empty value rather than committing zero bytes.
Top-level scalar fixture (`true`, `42`, `"x"`)
SupportedA fixture that is a bare scalar is valid JSON and validates green. If your loader expects an object/array, the mismatch is a test expectation, not a syntax error — but it's worth noticing in the parsed output before committing.
Unescaped control character in a string value
RejectedA raw tab/newline pasted into a string (instead of \t/\n) fails with Bad control character in string literal. Common when pasting multi-line copy into a locale string. Escape it, or move long text out of the JSON entirely.
File over the 2 MB free limit
BlockedFree tier caps files at 2 MB. Most fixtures and locale bundles are well under this. A multi-megabyte fixture is usually a sign the test data should be generated or loaded from a database, not committed. Larger files validate on Pro (100 MB) / Developer (5 GB).
Frequently asked questions
Will a file that passes here pass my husky/lint-staged hook?
Yes, if the hook uses an RFC 8259 parser — jq empty, node -e 'JSON.parse(...)', and prettier all do. This validator uses V8 JSON.parse, the same engine, so the manual check and the automated hook reach identical verdicts. The only difference is the hook runs on every staged *.json automatically.
What's the simplest pre-commit hook to mirror this check?
lint-staged with "*.json": "jq empty". jq empty parses each staged JSON and exits non-zero if any file is invalid, which aborts the commit. Set it up with npx husky init and npx lint-staged in .husky/pre-commit. It's the exact RFC 8259 check this page runs, enforced for the whole team.
Does it flag duplicate keys, which I sometimes get from merge conflicts?
No. JSON.parse accepts duplicate keys and keeps the last value, so this validator (and jq, and a jq-based hook) passes them. Merge-conflict resolutions are the usual source — review them by eye. There's no JSON tooling in this suite that flags duplicate keys.
Should I use Prettier instead of this for pre-commit?
They complement each other. Prettier in lint-staged reformats and re-stages (and rejects invalid JSON as a side effect), enforcing both validity and style. This validator is the quick manual look while you're editing a single file, and it's handy for files Prettier is configured to ignore. Use both.
Is my staged file uploaded anywhere?
No. Validation runs entirely in your browser via JSON.parse. Fixtures, seed data, and mock payloads never reach a JAD Apps server. DevTools shows no network request on Validate.
Does the 'Strict mode' checkbox let me allow comments in fixtures?
No. The parser is strict RFC 8259 regardless of the checkbox — comments and trailing commas are always rejected. If you genuinely need comments (a JSONC fixture), don't run it through a strict pre-commit check; use a JSONC-aware loader.
How do I exclude large generated JSON from the pre-commit hook?
In lint-staged, scope the glob — e.g. "src/**/*.json": "jq empty" instead of "*.json" — or add a negation in your tooling. Better still, gitignore generated JSON so it's never staged. Files git doesn't track are never seen by lint-staged.
Can I validate all staged JSON at once here?
No — the UI is one file at a time (free batch is 1). For 'all staged files' you want the automated hook (lint-staged runs jq empty across every staged *.json). This tool is for the focused single-file check.
Why does a trailing comma break npm but look fine in my editor?
Many editors render JS-style files leniently and some even auto-allow trailing commas in JSON view, but npm/node parse package.json with strict JSON.parse, which forbids them. This validator matches npm's strictness, so it catches what your editor's preview might forgive.
Will it tell me if my fixture is missing a field my test needs?
No — that's structural, not syntactic. The validator confirms the fixture is parseable and shows you the parsed shape so you can eyeball the fields. For automated field/shape checks, generate a schema with the JSON Schema Generator and validate the fixture against it.
What about a BOM that my editor keeps re-adding?
The validator trims the BOM and passes the file, but BOMs cause noisy diffs and can trip non-JS tools. Configure the editor to save as 'UTF-8' (not 'UTF-8 with BOM') for your .json files so the byte never enters git.
Why is the parsed-output preview cut off?
The on-screen pretty print stops at 5,000 characters for performance. The valid/invalid verdict still covers the whole file, and Copy returns the complete re-serialised output. The truncation is display-only.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.