dev101.io

JSON Formatter

Format, validate, and minify JSON in your browser — privately.

Runs online in your browserNo installNo accountFree

Loading tool…

How to use JSON Formatter

  1. Paste your JSON into the input panel on the left. The validation badge flips to green as soon as the document parses cleanly.
  2. Pick an indent style (2 spaces, 4 spaces, or tab) from the Indent dropdown — the default is 2 spaces, which matches most linters and Prettier defaults.
  3. Press the Format button or use ⌘/Ctrl + Enter to pretty-print. Changing the indent while an output is on screen re-formats automatically.
  4. Press the Minify button or use ⌘/Ctrl + Shift + M to strip all whitespace. The byte counter below the panels shows exactly how much smaller the wire payload becomes.
  5. If the input fails to parse, read the red error banner — it includes the native parser message plus a human-readable line and column pointing at the first offending character.
  6. Click Load sample to drop in a reference document when you want to compare indent styles or check what the output layout looks like before pasting real data.
  7. Click Copy output to put the result on your clipboard, or Share to generate a URL that restores your input and indent choice on another device.
  8. Click Clear to reset both panels and return focus to the input textarea so you can start the next paste without reaching for the mouse.

JSON Formatter

A keyboard-driven JSON formatter, validator, and minifier that runs entirely in your browser. Paste raw JSON, get a clean, indented, validated document back. Or reverse it — minify pretty-printed JSON down to the smallest valid representation. The validator is live: the badge on the input panel flips to green the moment your input becomes a legal JSON document, and red the moment it stops, with a line/column reference to the offending character. No payload ever leaves your device.

Why a dedicated JSON formatter

Most online JSON formatters do the work on a server. That makes them fast to build, but it also means every API response, config file, webhook body, and error payload you paste gets logged somewhere you do not control. For anyone working with production data — JWTs, access tokens, PII, payment metadata — that is a non-starter. A "pretty print" button is not worth leaking a production log line to a third party.

It is also the wrong architecture. JSON parsing is a pure function of a string. The browser you are already using ships a compliant parser in JSON.parse and a serialiser in JSON.stringify; there is no reason to send bytes across a network to run those two functions on a different computer. This tool uses the parser that is already on your machine, wraps it in a thin Result<T, E> interface so errors are typed rather than thrown, and renders the output next to the input. You get the same correctness guarantees as the V8 parser inside Node.js — because it is, literally, the same parser — without the round-trip.

The formatter also doubles as a strict validator. When a document passes here it will pass in JSON.parse, jq, Go's encoding/json, Python's json module, and every RFC 8259-compliant parser. A pretty-printer that silently accepts JSON5 comments is worse than useless — it makes you think your document is portable when it is not.

What it does

  • Pretty-print with 2 spaces, 4 spaces, or tab indent. Output is RFC 8259-compliant and round-trips through any standard parser. Switching indent with output already on screen re-formats immediately.
  • Minify to the smallest valid representation: whitespace between tokens stripped, strings byte-identical. Useful for HTTP payload budgeting, single-line JSON columns in Postgres or BigQuery, curl -d invocations, and environment-variable config.
  • Validate as you type. The input badge goes green when the document parses, red when it does not; failure shows the native JSON.parse message plus a line and column derived from the error's byte position.
  • Strip BOM automatically. Windows tooling, older Excel, and misconfigured PowerShell redirections emit a leading UTF-8 byte order mark that breaks naive parsers; the tool removes it before parsing.
  • Trim whitespace at the edges. Leading and trailing spaces, newlines, and tabs pasted from a log file or chat message are ignored; the reported byte count is the trimmed size.
  • Byte counters and savings after minify. Both sides are measured with TextEncoder so multi-byte characters count correctly, and the percentage is rounded to one decimal.
  • Share via URL hash. Input and indent serialise into #s=… so pasting the link into Slack or a ticket reloads the exact same state on another machine. The hash is never transmitted.

Under the hood

The transform layer is three pure functions: validate, format, and minify. Each takes a string and returns a typed Result. That shape is deliberately boring — the UI never handles exceptions, never blocks on a worker, never shows a spinner. A 2 MB document parses in single-digit milliseconds and the output panel repaints on the next animation frame.

Error reporting is where the tool invests effort above what JSON.parse gives you. Native browser errors look like Unexpected token } in JSON at position 347 — accurate, but not useful when you are staring at a 50-line document. The tool pulls the byte offset out of that message, walks the trimmed input counting newlines, and appends (line 12, column 8) — the same notation your editor uses. Firefox emits a slightly different shape (line L column C) and the extractor handles both. When the parser gives no position at all the message is surfaced verbatim, so you are never left guessing whether the tool is hiding information.

The live validation badge uses useMemo keyed on the input string, so it runs on every keystroke without re-rendering the rest of the page. Formatting and minifying trigger only on explicit action — Format, Minify, or changing the indent while output already exists — which keeps the URL hash from being rewritten on every keystroke.

Indent mode is typed as the literal union 2 | 4 | "tab" end-to-end, so the dropdown cannot desync from the serialiser. No custom indent code path — the browser's JSON.stringify does the work, so output is guaranteed RFC 8259-clean.

Privacy, and runs entirely in the browser

No analytics on input content and no third-party script in the transform path. The page ships with no JSON-handling endpoint — open the network tab while you format and you will see zero outbound requests tied to the transform. We do load Google Analytics 4 for aggregate page-view counts (cookieless by default, honours DNT/GPC — details), but it never receives textarea content or URL-hash state. The Share button encodes a base64url snapshot of your input and indent into the URL fragment (#s=…), which by HTTP design is never sent to any server. Sharing a link is a peer-to-peer transfer of state, not an upload; even URL-rewriting intermediaries like Slack unfurling or corporate proxies only see the path and query, never the hash.

That matters more than it sounds. Production JSON routinely contains session tokens, API keys, JWT claims, webhook signing secrets, and internal service names. Pasting any of that into a server-rendered formatter is, functionally, emailing it to whoever runs the site.

Keyboard shortcuts

  • ⌘/Ctrl + Enter — Format the current input.
  • ⌘/Ctrl + Shift + M — Minify the current input.
  • ⌘/Ctrl + K — Open the global command palette and jump to another tool without leaving the keyboard.

The shortcuts work even when focus is in the input textarea, because they include a modifier key — typing a literal newline into the input is still Enter alone.

What's not supported (yet)

  • JSON5 / JSONC — comments, trailing commas, and unquoted keys are rejected. Strict-mode behaviour on purpose; a relaxed-parser tool for tsconfig.json and friends is on the roadmap.
  • Sort keys / canonicalise — no alphabetical-sort toggle yet. Canonical JSON for hashing or snapshot tests is planned alongside a JCS (RFC 8785) mode.
  • JSON Schema validation — this tool checks syntax, not shape. A forthcoming validator will handle $ref, oneOf, allOf, and format assertions.
  • Structural diff — see the JSON Diff tool for key-by-key comparison of two documents.
  • Path querying — for extracting a value from a large document by JSONPath, use the JSON Path tool.
  • Streaming — documents bigger than browser memory belong in a CLI like jq or a streaming parser such as clarinet.
  • Big-integer preservation — numbers round-trip through IEEE 754 doubles, so values above 2^53 lose precision. Represent them as strings if exactness matters.

Related tools

  • JSON Minify — single-purpose minifier for one-click strip-whitespace without the full formatter UI.
  • JSON Diff — key-by-key structural comparison of two documents with added, removed, and changed paths called out inline.
  • JSON Escape — quote a raw string for embedding in a JSON value, or un-escape a quoted one, with correct \u, \n, and surrogate-pair handling.
  • JSON Path — evaluate a JSONPath expression against a document and see the matching subtree.
  • YAML Formatter — same "format and validate locally" contract for YAML configs.
  • CSV to JSON — convert a CSV export into a JSON array of objects ready to paste back into this formatter.

Frequently asked questions

Is the JSON I paste sent to your servers?

No. Every transformation runs locally in your browser. There is no JSON-handling endpoint on dev101.io — we deliberately avoid round-trips so your data, including secrets that might appear in payloads, never leaves your device. The "Share" button encodes state into the URL hash (the part after `#`), which is also never sent to a server by browsers.

Why does my JSON with comments or trailing commas fail to parse?

Strict JSON (RFC 8259) does not allow comments or trailing commas. If your input is JSON5, JSONC, or another superset, it will be rejected here. We chose strict mode so the formatter doubles as a validator — running it tells you whether the document will work in environments like `JSON.parse`, `jq`, or any standard parser. A dedicated JSON5/JSONC tool is on the roadmap.

How big a document can I format?

Anything that fits in browser memory. In practice, modern browsers handle multi-megabyte documents fine. The bottleneck is the textarea rendering, not the parser. If you're working with hundreds of MB, format in chunks or use a streaming CLI like `jq` instead.

Does the URL share link work for very large payloads?

It works up to roughly 50 KB of input — beyond that, browsers and servers may truncate or reject the URL. For larger payloads, use the "Copy output" button and paste the result wherever you need it.

What do the line and column numbers in the error message refer to?

They point to the 1-based position in the input after a leading UTF-8 BOM (if any) has been stripped and leading/trailing whitespace trimmed. The tool extracts a `position N` offset from the native `JSON.parse` error and converts it into a line/column pair by walking the trimmed input. That means the reported location matches what you see in the input textarea, not the raw byte offset the browser engine originally emitted. If your editor shows a slightly different column because it counts tab stops differently, the line number is the reliable anchor.

Does the formatter preserve key order, number precision, or duplicate keys?

Key order is preserved exactly as the parser sees it — `JSON.parse` keeps insertion order for string keys, so `{"b":1,"a":2}` stays in that order through format and minify. Numbers are round-tripped through JavaScript's IEEE 754 double, so integers up to 2^53 and standard decimals are exact, but values like `12345678901234567890` lose precision — if that matters, use a string field instead. Duplicate keys are collapsed by the parser (the last wins), which is standard RFC 8259 behaviour; the formatter does not flag them, because by the time it sees the value the duplicate is gone.

What does the "bytes saved" counter after minify actually measure?

It measures the UTF-8 byte length of your input minus the UTF-8 byte length of the minified output. Both sides use `TextEncoder().encode(...).length`, so multi-byte characters (emoji, non-Latin script) count correctly and you get the figure you would actually see on the wire or on disk — not a character count. The percentage is computed against the original byte length and rounded to one decimal place. Empty input reports zero rather than dividing by zero.

Related tools