A linter for JSON, I-JSON, JSONC, HuJSON (JWCC), JSON5 and JSON Lines. It finds syntax errors and duplicate keys, and tells you the exact line and column:
config.json:4:5: error[duplicate-key] duplicate key $.server.port (first at 2:5)
package.json:3:3: error[syntax] expected `,` or `}`, found `"`
events.jsonl:12:1: error[syntax] blank line; JSON Lines needs a value on every line
- Six dialects, one parser. Each file is checked in the dialect its extension implies.
- JSON Schema with positions.
--schemapoints each schema error at its value's line and column. - Fits CI. Use it as a pre-commit or prek hook, a GitHub Action, a command or a Rust library.
- Fast. It was check-json5, a Python hook, until 2.0 rewrote it in Rust. It is up to 1,398 times faster.
Add this to .pre-commit-config.yaml (for pre-commit or
prek):
- repo: https://github.com/simonvanlierde/jsonvet
rev: v3.0.0
hooks:
- id: jsonvetIt checks files by name: .json, .jsonc, .json5, .jsonl, .ndjson, .geojson, .jsonld,
.hujson, .jwcc, .code-workspace, .code-snippets, .webmanifest and .babelrc.
Options go in args. This holds package.json to strict JSON:
- id: jsonvet
args: [--strict, package.json]The hook installs a prebuilt binary from PyPI, so you need no Rust.
- uses: simonvanlierde/jsonvet@v3The step fails when a file has an error, and each problem shows up as an annotation on the pull request. It runs on Linux, macOS and Windows runners. Its optional inputs are in the reference.
uvx jsonvet config/*.json # run it from PyPI without installing
pip install jsonvet # install it as a Python package
cargo install jsonvet-cli # build it from source
cargo binstall jsonvet-cli # download the binary from the GitHub releaseEach GitHub release after 3.0.0 also has
the jsonvet binary for Linux (glibc and musl), macOS and Windows, on x86-64 and ARM64, as
jsonvet-<target>.tar.gz (.zip on Windows), with a SHA256SUMS file and a CycloneDX SBOM.
To check that a file came from this repository's release workflow, see
SECURITY.md.
It exits 0 when no file has an error, 1 when one does, and 2 for a usage error. jsonvet --help
lists every option.
use jsonvet::{check, Config, Dialect};
let report = check("{\"a\": 1, \"a\": 2}", &Config::new(Dialect::Json));
for problem in report.problems() {
let at = report.location(problem.offset());
println!("{}:{}: {}[{}] {problem}", at.line, at.column, problem.severity(), problem.rule());
}The API is on docs.rs.
| Dialect | Adds to JSON | Default for |
|---|---|---|
json |
nothing (RFC 8259) | none; choose it with --dialect or --strict |
i-json |
stricter content (RFC 7493) | none |
jsonc |
// and /* */ comments |
.jsonc, .code-workspace, .code-snippets |
jwcc |
comments and trailing commas (HuJSON) | .hujson, .jwcc |
json5 |
the JSON5 1.0.0 grammar | .json, .json5, .geojson, .jsonld, .webmanifest, .babelrc |
jsonl |
one JSON value per line (JSON Lines) | .jsonl, .ndjson |
Any other extension is checked as JSON5. .json defaults to JSON5 because most .json files that
fail as JSON are config files with comments. To check a file as strict JSON, use
--dialect json:package.json.
| Rule | Reports | Default |
|---|---|---|
syntax |
the first syntax error; checking stops there | error |
duplicate-key |
a key repeated in one object, with both positions and its path | error |
invalid-number |
a number a double cannot hold, or an integer past ±(2⁵³ − 1) | error in i-json, off elsewhere |
invalid-code-point |
a lone surrogate or a noncharacter in a string | error in i-json, off elsewhere |
byte-order-mark |
a byte order mark at the start of the file | warning in json and i-json, error in jsonl |
missing-final-newline |
a JSON Lines file that does not end with a newline | warning in jsonl |
Change a rule's severity with --error, --warn or --ignore. Warnings fail a run only past
--max-warnings N.
To keep settings out of the command line, put them in jsonvet.toml, or in a [tool.jsonvet]
table in pyproject.toml:
rules = { byte-order-mark = "ignore", missing-final-newline = "error" }
# Checked in order; the last matching entry wins.
dialects = [
{ files = ".vscode/*.json", dialect = "jsonc" },
{ files = "package.json", dialect = "json" },
]jsonvet looks for it in the working directory and its parents, up to the repository root. Options on the command line win. How globs match is in the reference.
--schema FILE checks each file against one JSON Schema, and reports each error at its value:
$ jsonvet --schema server.schema.json server.json5
server.json5:3:9: error[schema] "80" is not of type "integer" (at $.port)
server.json5:6:3: error[schema] Additional properties are not allowed ('debug' was unexpected) (at $.debug)
In the hook, give each schema its own entry:
- id: jsonvet
name: jsonvet (server schema)
files: ^config/server\.json5$
args: [--schema, schemas/server.schema.json]Schema errors are always errors. jsonvet never fetches a schema from the network or another file,
so every $ref must point inside the schema file. The other limits are in the
reference.
--format sarif prints a SARIF 2.1.0 document
for GitHub code scanning. The Action's sarif input writes one for you to upload:
jobs:
jsonvet:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: simonvanlierde/jsonvet@v3
with:
sarif: jsonvet.sarif
# Upload when jsonvet found problems too: that is when the step fails.
- uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
if: success() || failure()
with:
sarif_file: jsonvet.sarifBump rev to the newest tag. The check-json5 hook id, the check-json5 command and --strict
keep working through 3.x.
For the files check-json5 knew, jsonvet gives the same pass or fail as 1.4.1, apart from a few
edge cases listed in
docs/design.md.
One change can fail a file that passed: .jsonc, .code-workspace and .code-snippets files are now
checked as JSONC, and .hujson and .jwcc files as JWCC. To keep the old verdict, add
--dialect json5:GLOB. The other changes are in the
CHANGELOG under 2.0.0 and
3.0.0.
jsonvet runs the json5-tests suite, edge-case snapshots, property tests, fuzzing, mutation testing and a comparison with check-json5 1.4.1, on Linux, macOS and Windows. A conformance matrix runs the same files against 18 other parsers in four languages:
| dialect | files | share of files where each parser gives the expected verdict |
|---|---|---|
| JSON5 | 166 | js-json5 100%, jsonvet-json5 100%, rs-json5 100%, py-json5 99%, py-pyjson5 99%, rs-serde-json5 94%, go-json5 91% |
| JSONC | 9 | js-jsonc-parser 100%, jsonvet-jsonc 100%, go-tidwall-jsonc 89%, js-strip-json-comments 89%, rs-jsonc-parser 78% |
| JWCC (HuJSON) | 10 | jsonvet-jwcc 100%, go-hujson 90% |
| JSON Lines | 11 | jsonvet-jsonl 100%, py-jsonlines 82%, go-json-stream 64%, rs-serde-json-stream 64% |
| Strict JSON (baseline) | 129 | js-json-parse 100%, jsonvet-json 100%, rs-serde-json 98%, py-json 96%, py-jiter 95% |
Run on 2026-10-19. The full grid, and where each expected verdict comes from: docs/conformance.md.
- Reference: every option and limit in detail.
- Design: why it works the way it does, with the speed figures.
- CHANGELOG: what changed in each release.
- CONTRIBUTING.md: how to build, test and release it.
Found a file jsonvet gets wrong? Please open an issue with the file and the command you ran. If it crashes or hangs jsonvet, report it privately as SECURITY.md says.
To cite jsonvet, use the DOI 10.5281/zenodo.23461825 or GitHub's "Cite this repository" button.
I (Simon van Lierde) started this as a fork of check-json5 by Ben Mares, which is based on the pre-commit-hooks repository by Anthony Sottile and contributors. It is MIT-licensed, like both of them.