▸ Free tool
JSON Formatter & Validator.
Format, minify and validate JSON — and when it breaks, get the line, the column, a caret under the offending character, and a straight answer about what to fix. Nothing leaves your browser.
▸ Strict on purpose
This validates against RFC 8259 — no comments, no trailing commas, no single quotes. If your file needs those, it is JSONC or JSON5, not JSON.
Runs in this tab · nothing is uploaded · validation is live under 2 MB
Paste JSON above to validate it.
▸ —
—
▸ Document stats
- Input
- —
- Output
- —
- Minified
- —
- Saved by minify
- —
- Keys
- —
- Max depth
- —
- Objects
- —
- Arrays
- —
The error message is the whole product
Formatting JSON is a one-liner — JSON.stringify(JSON.parse(text), null, 2). What actually costs you time is a 4,000-line payload that will not
parse and an engine that says Unexpected token and nothing else. Every engine words it differently: V8 gives you a byte
offset, SpiderMonkey gives you a line and column, JavaScriptCore usually gives
you neither.
So this tool does not lean on the engine. It runs its own scanner over
the document first, stops at the first thing the grammar forbids, and
reports the line, the column, the surrounding lines, a caret under the
exact character, and what to change. Trailing comma, single quotes,
unquoted key, missing comma, stray BOM, NaN, a Python repr that was printed instead
of serialized — each one gets its own explanation instead of a generic parse
failure. JSON.parse still does the real
decoding afterwards; the scanner exists so the failure is legible.
One honest limit: a browser textarea is the wrong place for a very large
file. Live validation switches off above 2 MB and the buttons take over.
Past a few megabytes, jq or python -m json.tool will be faster and will not fight your scroll position.
JSON vs JSONC vs JSON5
Most "invalid JSON" is valid something-else. Comments are the clearest
case: tsconfig.json, .eslintrc.json and VS Code's settings all carry //
lines, and they all parse — because the tools reading them use a JSONC parser,
not JSON.parse. The file extension
lies.
| JSON (RFC 8259) | JSONC | JSON5 | |
|---|---|---|---|
| Comments | |||
| Trailing commas | usually tolerated | ||
| Unquoted keys | |||
| Single-quoted strings | |||
| NaN / Infinity | |||
| Hex numbers, leading + | |||
| JSON.parse accepts it | |||
| Where you meet it | APIs, webhooks, config, logs | tsconfig.json, VS Code settings, .eslintrc | hand-edited config files |
The rule of thumb: if a human edits the file, a dialect with comments is a kindness. If a machine sends it over a wire or stores it in a column, stay on strict JSON, because you do not control which parser is at the other end.
Minifying matters less than compression
Pretty-printing a document with 2-space indent typically adds 10–20% to
its size, and the stats bar above shows you exactly how much. Then gzip
or brotli comes along and eats almost all of it, because indentation is
the most compressible thing in the file — a long run of identical bytes.
Minifying a response that is already served with Content-Encoding: gzip usually moves the transferred size by a couple of percent.
Minifying does pay where compression is not in the path: values written
into localStorage or IndexedDB against
a quota, queue payloads bumping a size ceiling (SQS caps a message at 256
KB), JSON stuffed into a database column, cached blobs in Redis, and prompts
sent to an LLM — where whitespace is billed as tokens, which you can measure
with the token counter. Turn compression on first. Then argue about whitespace.
Key order is not data
RFC 8259 says an object is an unordered collection of name/value
pairs. Most parsers preserve insertion order anyway, which teaches people
to rely on it — right up until JavaScript does not. Any object key that looks
like an array index is hoisted to the front in ascending numeric order, so
{"10":"a","2":"b"} re-serializes as
{"2":"b","10":"a"}. That is the
language specification, not a bug in this tool, and the stats bar flags
documents where it can bite.
Sorting keys is still useful — for diffs that stop churning, for cache
keys that stay stable, for snapshot tests that stop flapping. The sort
here is recursive and compares by code unit rather than localeCompare, so you get the same output on every machine in every locale. What it
is not is a canonicalization scheme: if you need a hash or a signature
over JSON, either hash the exact bytes you received — which is what
every webhook HMAC check does, and why you must never re-serialize
before verifying — or use a real canonical form such as JCS (RFC 8785).
The 253 bug that silently corrupts IDs
JSON numbers have no integer type. Every one of them lands in an
IEEE-754 double, which represents integers exactly only up to
Number.MAX_SAFE_INTEGER — 9007199254740991.
One past that,
JSON.parse('{"id":9007199254740993}')
hands you 9007199254740992. No exception, no warning, just a different record.
Snowflake IDs from X and Discord, Postgres bigint primary keys, order references and ledger amounts in minor units all live
in that range, which is exactly why mature APIs ship them as strings.
This tool scans the raw text for integer literals and reports every one
that fails to round-trip, with the value you will actually get back —
paste the "Big ID" sample and watch. Newer V8 can hand a
JSON.parse reviver the untouched source
text (with JSON.rawJSON for the trip back
out), but support is not universal yet. The portable fix has not changed:
serialize big identifiers as strings, and never let one make a round trip
through a JavaScript number.
Duplicate keys, and NDJSON for logs
RFC 8259 says names within an object should be unique — should, not
must. So parsers improvise. JavaScript and Python keep the last occurrence,
some libraries keep the first, a few return both. When a proxy validates a
request with one parser and the backend reads it with another, that disagreement
becomes a real attack: {"role":"user","role":"admin"} passes the check and lands as something else. The stats bar names every
duplicate key and the line it is on.
The other thing people paste in here is not one document at all. Logs, exports and streaming APIs usually emit NDJSON — one complete JSON value per line, no wrapping array, no commas between records. It appends cheaply, it survives truncation (you lose the last line, not the file), and it streams line by line instead of forcing a reader to hold the whole array in memory, which is the same argument as structured logging in logging vs monitoring. A strict parser rejects it, correctly. When this tool sees a second top-level value it says so instead of pointing at a random bracket — split on newlines and parse each line on its own.
Questions, answered
Why doesn't JSON.parse tell me which line the error is on?
Because the message is not standardised. V8 (Chrome, Node, Edge) appends "at position 42 (line 3 column 5)", SpiderMonkey (Firefox) says "at line 3 column 5 of the JSON data", and JavaScriptCore (Safari) often gives you a bare "JSON Parse error: Expected '}'" with no position at all. This tool does not rely on any of that: it scans the document itself, so you always get the line, the column, a caret under the exact character, and a plain explanation of what is wrong.
Are trailing commas allowed in JSON?
No. RFC 8259 has no trailing commas anywhere — not after the last array element, not after the last object member. JavaScript, JSON5 and most JSONC parsers accept them, which is why they show up constantly in files copied out of an editor. Delete the comma before the closing bracket and the document parses.
Does minifying JSON make my API faster?
Barely, if you have gzip or brotli switched on — compression already collapses repeated whitespace, so stripping it usually buys a few percent of the compressed payload. Minifying pays where compression does not run: browser storage quotas, queue message size limits, database columns, Redis memory, and LLM prompts where every space is billed as a token. Turn on compression first, then worry about whitespace.
Why did my large ID change after JSON.parse?
JSON numbers become IEEE-754 doubles, which hold integers exactly only up to 2^53 - 1 (9007199254740991). Anything larger is rounded to the nearest representable value with no error and no warning: 9007199254740993 comes back as 9007199254740992. Snowflake IDs, Postgres bigint keys and payment references all land in that range, which is why serious APIs send them as strings. This tool flags every integer in your document that fails to round-trip.
Is my JSON uploaded anywhere?
No. The scanner, JSON.parse, the formatter and the key sort all run in this tab — there is no request, no logging and no server involved. Open your network tab while you paste, or load the page once and switch off your network. That matters here more than on most tools: the fastest way to leak a production payload is to paste it into a formatter that posts it to a backend.
Can this tool handle comments in JSON, like tsconfig.json?
It detects them and tells you exactly where they are, but it will not parse them, because // and /* */ are JSONC, not JSON. tsconfig.json, .eslintrc and VS Code settings are read by JSONC-aware parsers; JSON.parse rejects all three. If you need to keep the comments, use a JSONC reader such as jsonc-parser or json5; if you need to ship the file over the wire, strip them.
Keep reading
-
▸ Tool
JSON to TypeScript
Once the payload parses, turn it into interfaces instead of typing them out by hand.
-
▸ Post
REST vs gRPC
What you give up with text-over-the-wire — and what a schema-first binary format buys back, including real integers.
-
▸ Post
Logging vs monitoring
Why your logs should be one JSON document per line, and what you get once they are.