JSON formatter, validator and tree viewer

Paste JSON to pretty-print it, minify it, check it, or walk it as a collapsible tree. Errors come back with a line, a column and a caret under the exact character that broke. Everything happens in this tab — what you paste never leaves your device.

Indent

Nothing you paste is uploaded. The parser, the formatter and the tree are plain JavaScript running inside this tab. There is no server here to send JSON to, no account, and no “share this snippet” link that stores a copy. Open your browser’s developer tools, switch to the Network panel and press Format: your document does not appear in a request, because there is no code on this page capable of sending it anywhere. Why that matters more than it used to →

Why an offline JSON tool matters now

In November 2025 the security research firm watchTowr Labs published findings about two of the most widely used online code-formatting utilities. Both offered a “save” or “share” feature that stores a submission on the server and hands back a short link. The links were generated from a predictable, enumerable identifier — so anyone willing to count upwards could walk the entire archive, including submissions the person who pasted them had never intended to publish.

The researchers reported recovering more than 80,000 stored files, over 5 GB of data accumulated across roughly five years. The contents were exactly what you would expect developers to paste into a formatter while debugging, and exactly what you would least like to find in a public bucket:

  • Active Directory credentials and domain administrator passwords.
  • Cloud provider access keys and API tokens.
  • Database connection strings, complete with hostnames and passwords.
  • Private keys, session tokens and CI/CD secrets.
  • Personal data belonging to customers of banks, government departments, healthcare providers, universities and — with a certain grim irony — cybersecurity companies.

To test whether anyone was actually harvesting the archive, the researchers planted canary credentials: realistic-looking but fake secrets that would report back if used. They were exercised within days. This was not a theoretical exposure of dormant data; it was a live, monitored supply of working credentials.

None of this required a breach. No attacker compromised a server. The data was simply stored, given a guessable address, and left reachable. The failure was architectural: the tool had somewhere to put your data, so eventually your data was there.

The structural fix is not a better privacy policy. It is having nowhere to store the data in the first place. This page has no upload endpoint, no database and no share link, so there is no archive of pasted JSON that could later be enumerated, subpoenaed, sold, or left in a misconfigured bucket. That property comes from the architecture, not from a promise.

Read the reporting yourself

What to do if you have used one of those sites

  1. Assume anything you pasted into a hosted formatter is public. Not “probably fine” — public.
  2. Rotate every credential that could have appeared in one of those payloads: database passwords, API keys, service-account tokens, webhook secrets, JWT signing keys.
  3. Check your provider’s access logs for use of those credentials from unfamiliar addresses, going back as far as your retention allows.
  4. Change the habit, not just the keys. Redact before you paste, or use a tool that has nowhere to put the data.

The wider lesson is not about two specific websites. It is that a formatter has no technical reason to be a network service at all. Parsing JSON is something every browser has done natively since 2011. A page that sends your document to a server to add whitespace to it has chosen to take custody of your data for no functional benefit.

How it works

Everything on this page is client-side JavaScript, split into two small files with no dependencies between them and nothing fetched from anywhere.

Parsing

Your text goes to the browser’s built-in JSON.parse. That is the same parser your application uses, written in C++ and shipped with the browser, so a document this page accepts is a document your code will accept. Nothing here uses eval or the Function constructor — a JSON tool that evaluated its input would be a genuine security hole, because a payload could then run code in your session rather than merely being read.

Explaining the failure

When JSON.parse refuses, its message is famously unhelpful and differs between engines: Chrome says one thing, Firefox another, Safari a third, and some of them give you a character offset with no line number. So this page runs a second pass — a strict RFC 8259 scanner written specifically to find and name the first mistake. It reports:

  • the line and column, converted from the character offset by counting newlines, plus the raw character index;
  • the offending line itself, with a caret under the exact character (long lines are windowed around the caret, so a 4 MB single-line document still gives you something readable);
  • a named cause where one can be identified — trailing comma, single-quoted string, unquoted key, NaN, a Windows path with an unescaped backslash, a byte order mark, a comment, two concatenated documents, an HTML error page arriving where JSON was expected.

The scanner is an explicit state machine with its own stack rather than a recursive function, so a pathologically nested document produces an error message instead of a “maximum call stack size exceeded”. It is cross-checked against JSON.parse on twenty thousand randomly mutated documents; the two agree on every one about whether the input is valid.

Formatting and minifying

Pretty-printing is JSON.stringify(value, null, indent); minifying is JSON.stringify(value). Both operate on the parsed value, not on the text, which is why the output is always canonically formatted and why formatting doubles as a validation step. Note the consequence: comments, trailing commas and original key order inside a re-serialised object are not preserved, because they were never part of the parsed value. Key insertion order is preserved, except that integer-like keys are hoisted and sorted — that is a rule of the JavaScript object model, not a choice made here.

The tree

The tree is built lazily. On load it opens about three hundred rows and stops; deeper nodes are constructed only when you expand them, and any node with more than two hundred children renders them in pages. That is what keeps a multi-megabyte array from producing hundreds of thousands of DOM nodes and freezing the tab. Collapsed containers show their size — {…} 12 keys, […] 340 items — so you can judge a subtree without opening it.

The tree is a proper ARIA tree widget: arrow keys move and open, collapses or jumps to the parent, Enter toggles, Home and End jump to the ends, and * opens all siblings of the focused node.

Escaping

Every fragment of your document that reaches the page is written with textContent, never by assembling an HTML string. Paste a value containing <img src=x onerror=alert(1)> and it renders as those literal characters in the tree, in the output pane and in any error message. For a tool whose entire input is untrusted text, that is the one bug that would matter most.

Worked examples

1. An API response that will not parse

You call an endpoint, the client throws, and the message is Unexpected token < in JSON at position 0. Paste the raw response body here and the answer is immediate:

<!DOCTYPE html>
<html><head><title>502 Bad Gateway</title></head>

The tool reports “This looks like HTML or XML, not JSON” at line 1, column 1. The endpoint did not return bad JSON; it returned a gateway error page with a text/html body, and your client tried to parse it anyway. The fix is not in your parsing code — it is to check response.ok and the Content-Type header before parsing, and to log the raw body when the check fails. See the full guide to that error.

2. A config file a human edited

Rejected
{
  // service settings
  'host': "db.internal",
  port: 5432,
  replicas: 3,
}
Valid
{
  "host": "db.internal",
  "port": 5432,
  "replicas": 3
}

Four separate violations, and the tool reports them one at a time in the order a parser meets them: the // comment at line 2 column 3, then the single-quoted key, then the unquoted port, then the trailing comma after 3. Every one of these is legal in a JavaScript object literal, which is exactly why they are the four most common mistakes in hand-written JSON. If you need comments in a config file, you need JSON5, JSONC or YAML — not JSON.

3. Finding one record inside a large payload

A 6 MB export from an analytics API, one enormous array of event objects. Reading it as text is hopeless. Paste it, press Format, switch to Tree, and the root shows […] 41,208 items without having rendered any of them.

Type checkout_failed into the filter. The search walks the parsed value, reports how many keys and values contain that text, and lists the first two hundred with their full paths — $[18422].event.name. Click one and the tree opens every ancestor down to that node, paging through the array in blocks of two hundred until it reaches index 18,422, and focuses it. The stats row tells you the document is 41,208 values deep at most five levels, which is usually enough to decide whether the shape is what your code expects.

4. Shrinking a payload before you ship it

A configuration blob checked into a repository, pretty-printed with four-space indent, is 214 KB. Press Minify and the stats row shows the output at 137 KB — a 36% reduction, entirely from whitespace.

Worth knowing before you act on that: if the response is served with Content-Encoding: gzip or br, most of that saving is already being made for you, because repeated runs of spaces compress extremely well. Minifying a gzipped API response typically buys single-digit percentages on the wire. Where minifying genuinely pays is in things that are not compressed in transit: values stored in a database column, embedded in a QR code, put in a cookie or a header, or held in memory as strings. More on when minifying is worth it →

5. The number that changed by itself

Paste {"id": 9007199254740993} and format it. The output reads 9007199254740992 — one less than you typed.

Nothing here is broken; this is IEEE 754. JavaScript numbers are 64-bit floats, which represent every integer exactly only up to 253 − 1 (9,007,199,254,740,991). Above that, values are rounded to the nearest representable double. Every JSON parser built on JavaScript numbers behaves this way, including the ones in your browser and in Node.js, so a large database ID or a Twitter-style snowflake must be transported as a string. If your API emits bare 64-bit integers, this page will show you the damage before your users find it.

Frequently asked questions

Is my JSON uploaded anywhere?

No. The page has no upload endpoint and no storage. Parsing, formatting, minifying, the statistics and the tree all run in JavaScript inside your tab, using the browser engine’s own JSON.parse. There is no code on this page that can send your document anywhere.

You do not have to take that on faith. Open developer tools (F12, or I on a Mac), select the Network panel, paste a document and press Format. As of today the page issues no request at all once it has loaded. You can also save the page to disk with Ctrl/S and use it with the network switched off entirely.

How large a document can I paste?

Documents of several megabytes are fine. Above roughly 300 KB the tool stops re-formatting on every keystroke and waits for you to press Format or Minify, because re-parsing a large document on each character typed is what makes other formatters freeze.

Two further guards apply to very large inputs: the text output pane renders only the first 250,000 characters (Copy and Download still give you the whole thing), and the tree builds nodes only as you expand them. The practical limit is your machine’s memory, not a size cap in the page.

What does “Unexpected token … in JSON at position N” mean?

It means the parser reached character number N — counting from zero, across the whole document including newlines — and found something that cannot legally appear there. The character it names is where the parser noticed the problem, which is often one or two characters after where you actually made it: a missing comma is reported at the value that follows it, not at the gap.

Paste the document here and you get the line, the column and a caret instead of a bare offset. The dedicated page works through every common cause with examples.

Why does my formatted output not match my input byte for byte?

Because the output is re-serialised from the parsed value rather than edited as text. Four things can legitimately change: whitespace and indentation, of course; non-ASCII characters written as \uXXXX escapes come back as the literal characters; numbers are normalised (1.50 becomes 1.5, 1e3 becomes 1000); and duplicate keys collapse to the last one, since an object cannot hold the same key twice.

Integer-like keys are also reordered ahead of string keys and sorted numerically. That is a rule of the JavaScript object model that every JavaScript-based JSON tool inherits. If exact byte preservation matters — for a signature, a hash or a canonical form — do not round-trip the document through any parser.

Are trailing commas or comments ever acceptable?

Not in JSON. RFC 8259 allows neither, and no conforming parser accepts them. They feel acceptable because JavaScript, Python, most linters and every code editor allow them, and because several JSON-adjacent formats deliberately add them back.

If you control the format, the options are JSON5 (comments, trailing commas, unquoted keys, single quotes), JSONC (comments only — what VS Code’s settings files use), YAML, or TOML. If you do not control the format, strip the extras before parsing. Beware of the obvious shortcut: a regular expression that deletes // comments will happily corrupt any string containing a URL.

Can it handle JSON Lines / NDJSON?

Not as a single document, and neither can any JSON parser — that is the point of the format. NDJSON is one complete JSON value per line, and the file as a whole is not valid JSON. Pasting it here produces “A second JSON value starts here” at the beginning of line two, which is the correct diagnosis.

Split on newlines and parse each line separately, or wrap the lines in [] with commas between them to inspect the whole file here as one array.

Why do my big integers lose precision?

Because JSON numbers become IEEE 754 doubles. Integers above 253 − 1 = 9,007,199,254,740,991 cannot all be represented, so they round to the nearest value that can be. This is not specific to this page: it happens in your browser, in Node.js, and in any language that maps JSON numbers to a 64-bit float.

The standard remedy is to transport large identifiers as strings. Several languages offer big-decimal parsing modes that keep the digits intact, but the moment the value crosses a JavaScript boundary the precision is gone again.

Are duplicate keys allowed?

RFC 8259 says names within an object should be unique, but does not forbid repeats, and it leaves the behaviour of parsers that receive them undefined. In practice JavaScript keeps the last occurrence, and so does this page. Other parsers keep the first, or raise an error, or return a multi-map.

That disagreement has been used as an attack: a payload with a key repeated twice can be validated by one service and acted on by another with a different value. If you are writing a producer, never emit duplicates. If you are writing a consumer of untrusted JSON, reject them explicitly.

Does it work offline?

Yes. Once the page has loaded, nothing further is fetched. Save it with Ctrl/S and it keeps working from the local file with the network disconnected — there are no CDN scripts or web fonts to go missing, and every link between the pages is relative.

Does it validate against a JSON Schema?

No. This checks syntax — whether the text is well-formed JSON — not whether the resulting document matches a schema, has the fields your API requires or uses the right types. Those are separate questions answered by a validator such as Ajv, jsonschema or python-jsonschema. Syntax validity is the prerequisite: a document that fails here cannot be schema-checked at all.

What are the statistics counting?

Input and Output are UTF-8 byte counts, not character counts — an emoji is four bytes, an accented Latin letter two. Keys is every key in every object added together, so a hundred objects with three keys each counts as three hundred. Max depth counts nested containers: a bare number is 0, [] is 1, {"a":[1]} is 2. Values is every node in the document, containers included.

Is the tree keyboard-accessible?

Yes. Tab into the tree and use and to move between visible rows, to open a node or step into it, to close it or jump to its parent, Enter or Space to toggle, * to open every sibling of the focused node, and Home / End for the first and last visible rows. It is a standard ARIA tree, so screen readers announce the expanded state and the level.

Use cases and limitations

What people use this for

  • Reading an API response. A minified response body pasted from the network tab becomes readable in one keystroke, and the tree lets you collapse the parts you do not care about.
  • Finding the syntax error in a config file. package.json, tsconfig.json, a Terraform variables file, a Kubernetes manifest — when a tool says only “invalid JSON”, this says which character.
  • Checking a payload before sending it. Paste a request body you are about to POST and confirm it parses and has the shape you meant.
  • Understanding an unfamiliar document. The tree plus the depth and key counts tell you the shape of a response faster than scrolling.
  • Shrinking a value. Minifying before writing to a column, a cookie, a QR code or an environment variable.
  • Comparing two versions. Format both with the same indent and paste them into any diff tool; identical formatting removes the whitespace noise.

What it deliberately does not do

  • Repair broken JSON. It tells you precisely what is wrong and where, but it will not guess where a missing brace belongs. Automatic repair is how a subtle corruption gets committed with confidence.
  • Accept JSON5, JSONC, HJSON or YAML. Comments, trailing commas, unquoted keys and single quotes are reported as the errors they are. A lenient parser would let you ship a file that your production runtime rejects.
  • Validate against a schema. Syntax only.
  • Preserve byte-for-byte formatting. Output is re-serialised from the parsed value; see the FAQ above.
  • Sort or transform. No key sorting, no JSONPath queries, no filtering language. For transformations, jq is the right tool and it runs on your machine.

Honest limitations

Numbers pass through JavaScript’s IEEE 754 doubles, so integers beyond 253 and decimals such as 0.1 are subject to the usual floating-point rounding. Very large documents are limited by tab memory: a browser tab that runs out will be terminated by the browser, and a document larger than a few hundred megabytes is a job for a streaming parser such as jq, ijson or Jackson’s streaming API, not for any browser page. Lone surrogates in \u escapes are accepted, matching JSON.parse, even though they cannot be encoded as valid UTF-8.

More JSON tools on this site

Related tools

See all the tools on this site →