jsonvet
jsonvet checks JSON, I-JSON, JSONC, HuJSON (JWCC), JSON5 and JSON Lines files for syntax errors, duplicate keys and the rules of each dialect. Every problem comes with its file, line, column and rule, and a duplicate key with the JSONPath and position of both copies:
config.json:4:5: error[duplicate-key] duplicate key $.server.port (first at 2:5)
package.json:3:1: error[syntax] comments are not allowed in json
events.jsonl:12:1: error[syntax] blank line; JSON Lines needs a value on every line
It is a pre-commit and prek hook, a command, and a Rust library. Until 2.0 it was check-json5, a Python hook; 2.0 is a rewrite in Rust that keeps its verdicts (see Coming from check-json5).
Use it as a hook
In .pre-commit-config.yaml (pre-commit or prek):
- repo: https://github.com/simonvanlierde/jsonvet
rev: v2.0.0
hooks:
- id: jsonvet
The hook installs a prebuilt binary from PyPI, so it needs no Rust. It checks files by name:
.json, .jsonc, .json5, .jsonl, .ndjson, .geojson, .jsonld, .hujson, .jwcc,
.code-workspace, .code-snippets, .webmanifest and .babelrc. In GitHub Actions each problem
also becomes an annotation on the pull request.
Dialects
Each file is checked in the dialect its extension implies, and --dialect or the config file can
choose another. Each step in JSON, JSONC, JWCC, JSON5 only adds syntax to the one before.
| Dialect | Adds to JSON | Default for |
|---|---|---|
json |
nothing (RFC 8259) | none; choose it with --dialect or --strict |
i-json |
stricter content (RFC 7493): lone surrogates, noncharacters and numbers a double cannot hold are errors | 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 |
.json files default to JSON5, because most .json files that fail as JSON are config files
with comments. Hold the ones a strict parser reads, such as package.json, to JSON with
--dialect json:package.json.
Rules
| 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 |
Warnings don't fail a run unless there are more than --max-warnings N.
Options and configuration
--dialect DIALECT[:GLOB] check files matching GLOB (every file, without one) as DIALECT,
or as `auto`, the extension's default; the last match wins
--strict GLOB the same as --dialect json:GLOB, kept from check-json5
--error RULE, --warn RULE, --ignore RULE
--max-warnings N
--config FILE, --no-config
A glob without / matches the file name at any depth, and one with / matches the whole path.
* also matches /. Paths are matched as given on the command line, so relative to the working
directory, even for globs from a config file; pre-commit runs from the repository root.
Settings can also live in jsonvet.toml, or in a [tool.jsonvet] table in pyproject.toml.
jsonvet looks in the working directory and its parents up to the repository root, and the command
line wins:
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" },
]
Exit status: 0 when no file has an error, 1 when one does, 2 for a usage error.
Other ways to run it
uvx jsonvet config/*.json # from PyPI, without installing
pip install jsonvet # the same binary, as a Python package
cargo install jsonvet-cli # from source
The library is the jsonvet crate:
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());
}
Coming from check-json5
Bump rev to v2.0.0. The check-json5 hook id, the check-json5 command and --strict keep
working until 3.0, and the check-json5 package on PyPI now installs jsonvet.
2.0 gives the same pass or fail as 1.4.1 for every file, the same exit codes and the same duplicate-key paths. Comparing both on 20,114 generated files in JSON5 and in strict mode, 40,228 runs in all, they agree on every one. What changes, and why, is listed in the CHANGELOG and in docs/design.md:
- each line gains the severity and rule, as in
error[duplicate-key], and syntax errors use jsonvet's own wording; - the hook also checks
.jsonland.ndjsonfiles; - lines end at every JSON5 line terminator (LF, CR, CRLF, U+2028, U+2029), so positions after a lone CR or U+2028 differ;
- the importable Python module is gone; the wheel brings the
jsonvetandcheck-json5commands.
Speed
Median wall time of one run over the files, from uv run scripts/bench.py on 12 cores of a
Ryzen 7 H 255 under Linux. Both tools give the same verdict in every case:
| Case | check-json5 1.4.1 | jsonvet 2.0 | Speed-up |
|---|---|---|---|
| 1,100 config files | 40 ms | 12.5 ms | 3x |
| 2.7 MB JSON | 41 ms | 9.8 ms | 4x |
| 2.7 MB JSON, duplicate key | 330 ms | 11.6 ms | 29x |
| 120 KB JSONC | 22 ms | 1.5 ms | 15x |
| 1.8 MB JSON5 | 11,641 ms | 8.3 ms | 1,398x |
1.4 sent JSON, and JSONC with its comments stripped, to Python's C parser, so those were already fast. Any other JSON5 syntax went to a pure-Python parser, and so did the second pass that finds a duplicate key's position. jsonvet does everything in one pass of one parser.
How it is tested
- the json5-tests suite, all 114 cases;
- a reviewed snapshot of edge cases in every dialect;
- property tests: no input makes it panic, every position lies on a character boundary and matches a naive recount;
- fuzzing with cargo-fuzz, against serde_json and the json5 crate as references;
- the file-by-file comparison with check-json5 1.4.1 above, in CI;
- CI on Linux, macOS and Windows, plus the oldest supported Rust (1.88).
Development
cargo test --workspace # tests, including json5-tests (git submodule)
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all
uv run scripts/differential.py # compare with check-json5 1.4.1
uv run scripts/bench.py # time against check-json5 1.4.1
cargo +nightly fuzz run check # fuzzing, from fuzz/
Clone with --recurse-submodules, or run git submodule update --init for the test suite.
Credits
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. 2.0 is a rewrite in Rust.
License
MIT, like the projects it started from (Ben Mares, pre-commit-hooks).
Metadata
Release files for jsonvet 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jsonvet-2.0.0.tar.gz | 48.7 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| jsonvet-2.0.0-py3-none-win_arm64.whl | Python 3 | none | Windows ARM64 | Details |
| jsonvet-2.0.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| jsonvet-2.0.0-py3-none-musllinux_1_2_x86_64.whl | Python 3 | none | Linux musl 1.2+ x86-64 | Details |
| jsonvet-2.0.0-py3-none-musllinux_1_2_aarch64.whl | Python 3 | none | Linux musl 1.2+ ARM64 | Details |
| jsonvet-2.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| jsonvet-2.0.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| jsonvet-2.0.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| jsonvet-2.0.0-py3-none-macosx_10_12_x86_64.whl | Python 3 | none | macOS 10.12+ x86-64 | Details |
Total release size: 4.8 MB
Release files / jsonvet-2.0.0.tar.gz
| Download URL | jsonvet-2.0.0.tar.gz |
|---|---|
| Size | 48.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3b74a6c770421ced56dc4a7e9f7b9afdfa4d3d44dc8faa635f8bf5f91159b75e
|
|
BLAKE2b-256 checksum How to use checksums |
0e111c73abef71c3d59e39f1cfdc8bb5107fce857dbebead0ddeb538c37c61e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / jsonvet-2.0.0-py3-none-win_arm64.whl
| Download URL | jsonvet-2.0.0-py3-none-win_arm64.whl |
|---|---|
| Size | 426.9 kB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
20ca65b4a4d39d5d3197bb7a1056a53cc435022f63f31dbbd588d8551059eced
|
|
BLAKE2b-256 checksum How to use checksums |
4fb28e6c6f89d3aaa2801a8aab87c248f3b6542dc26853d3450f9f348cf6c48b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / jsonvet-2.0.0-py3-none-win_amd64.whl
| Download URL | jsonvet-2.0.0-py3-none-win_amd64.whl |
|---|---|
| Size | 458.8 kB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
c6ac0bdd5a63c25357d310f8029fc9eb29c450215e34f1858079877e661b3bb1
|
|
BLAKE2b-256 checksum How to use checksums |
2c5a4c5677262e5e2c4f8c1a2103b3a2e7e5f8d361e6a6cf95159528d5503e49
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / jsonvet-2.0.0-py3-none-musllinux_1_2_x86_64.whl
| Download URL | jsonvet-2.0.0-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 721.7 kB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
d8297623d4a7dbb4a321891830ea8969e18386a3047dc764ab58b12c22b7817c
|
|
BLAKE2b-256 checksum How to use checksums |
d30abc7421a0abe7b175136f58d7b2c08d59cf40d5c7b8a4f018676095d515d4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / jsonvet-2.0.0-py3-none-musllinux_1_2_aarch64.whl
| Download URL | jsonvet-2.0.0-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 677.5 kB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
393cbcf5240a7d321c5428aea1ce849fedbd6880f54dfe65f29f02f702f4a4fa
|
|
BLAKE2b-256 checksum How to use checksums |
f9dd85e138381a0eacd9d068c0ba24131123da2a09166955a5cba61c7289f394
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / jsonvet-2.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | jsonvet-2.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 630.6 kB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
c09e53fb85fb66744787bc96226f9fdd360c39744219df37991d04c637f34c75
|
|
BLAKE2b-256 checksum How to use checksums |
6336a2db64e70195413880282c5f3dda50c85a7d78fe2698ceceea46d60575b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / jsonvet-2.0.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | jsonvet-2.0.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 591.5 kB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
da227e597c34a0a91379d0ee09d541f34b977d7ed639e4e4899484d3bf7a0d0a
|
|
BLAKE2b-256 checksum How to use checksums |
39ff59ce59ff1f91f554bb81e2d56c1f3ce4e88a0bc9c78b61f2cdabd79487f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / jsonvet-2.0.0-py3-none-macosx_11_0_arm64.whl
| Download URL | jsonvet-2.0.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 573.4 kB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
ce8bb386cd838527bc785a8025118ce59850f154b5d7fcc0fa95cb5d25424625
|
|
BLAKE2b-256 checksum How to use checksums |
2f9965dd1e54dd65faaecdffaf1753a2cdf87d306c8caa7936068c280d0b3fad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / jsonvet-2.0.0-py3-none-macosx_10_12_x86_64.whl
| Download URL | jsonvet-2.0.0-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 623.7 kB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
c5bb8a72dbfa3d9421143b952fef196947f15cc4aff67ef7597ddd586df3d866
|
|
BLAKE2b-256 checksum How to use checksums |
1909ec2f1983a26c9fb7234001b7f8ed3f042979818ea532d75e9b3381a64dca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency log