What validates your JSON
Parsing is done by the browser's own JSON.parse, so "valid" here means valid to the same implementation that will consume the data in any JavaScript runtime — not to a hand-written parser that might be more forgiving. Formatting is JSON.stringify with your chosen indent. Everything re-runs about a fifth of a second after you stop typing.
Two things are then checked that the parser alone will not tell you. The first is the node count, which walks the parsed structure using an explicit stack rather than recursion — a deliberate choice, because a recursive walk exhausts the JavaScript call stack somewhere around ten to fifteen thousand levels of nesting, while JSON.parse itself comfortably handles far deeper documents. Using a loop means the counter never fails on input the parser accepted.
The second is duplicate keys, and it is the feature most formatters lack. {"id": 1, "id": 2} is valid JSON, and the parser silently keeps only the last occurrence — the first value vanishes with no error anywhere. To surface that, the tool re-walks your raw text with a small scanner looking only at object keys, and warns you which names repeat and that the last one wins. If you have ever debugged a config where a setting was mysteriously ignored, this is often why.
The JSONPath subset in the query box
The query field implements a practical subset of JSONPath, evaluated against the parsed document. When a path is active, both the code and tree panes show the extracted result rather than the whole document, and the hint beside the box reports how many nodes matched.
| Syntax | Meaning | Example |
$ | The root document | $ |
.name | Child by key | $.user.email |
['name'] | Child by key, for names with dots or spaces | $['content-type'] |
[n] | Array element by index; negative counts from the end | $.items[0], $.items[-1] |
[*] or .* | Every element or every value at this level | $.users[*].name |
.. | Recursive descent — search at any depth below here | $..id |
What is not supported: filter expressions such as [?(@.price<10)], array slices such as [1:3], and unions such as [0,2]. A path using those reports a parse error rather than silently returning the wrong nodes. For anything more elaborate, extract the subtree you need here and process it in code.
Where a round-trip changes your data
Large integers lose precision, silently. JSON numbers have no size limit, but JavaScript parses every one into a 64-bit float, which represents integers exactly only up to 9,007,199,254,740,991. Paste 9007199254740993, press Format, and you get 9007199254740992 back — no warning, because as far as the language is concerned nothing went wrong. Since 64-bit database IDs, Twitter-style snowflake IDs and some financial values live above that line, treat this tool as read-only for such documents, or have your API emit those fields as strings.
Numeric-looking keys get reordered. Formatting rebuilds the object, and JavaScript specifies that integer-like keys are enumerated first, in ascending numeric order, before any other keys in insertion order. So {"2":"a","1":"b","x":1} reformats to {"1":"b","2":"a","x":1}. The data is equivalent under the JSON spec, which defines objects as unordered, but if something downstream depends on key order, this is a change you did not ask for.
Strict JSON only. Comments, trailing commas, single-quoted strings, unquoted keys and NaN or Infinity are all rejected — correctly, since none is JSON. If you are editing a .jsonc or JSON5 config, strip the comments before pasting. There is also no schema validation here: the tool tells you the syntax is well-formed, not that the document matches what your API expects.
Your input is saved locally. So you do not lose work on an accidental reload, the contents of the input box are written to this browser's local storage, capped at 200,000 characters. Nothing leaves the device, but it does persist on it — so on a shared or public machine, clear the box when you are done, and be aware that anything past that cap is restored truncated and will no longer parse.
Everything happens on the main thread. Parsing, highlighting and tree building are synchronous, so a very large payload — tens of megabytes — will make the tab unresponsive while it works. Tree view is the heaviest part, since it creates a DOM element for every value. On a big document, use the query box to narrow to a subtree first.
Working faster with it
Use the two views for different jobs. Tree view is for orientation on an unfamiliar payload — every node shows its key or item count, so you can see the shape of an API response without scrolling through it. Code view is for reading and copying actual values. The Format and Minify buttons rewrite the input box itself, so you can paste minified data, expand it, edit it, and minify it again before sending it on.
Sort keys is the underrated option. Two API responses that differ only in key order are impossible to diff usefully; sorting both first makes a comparison meaningful. Feed the results to JSON diff to see exactly what changed between two payloads.
On error messages: the line and column are derived from the browser's own parser error, so the exact wording varies between Chrome and Firefox even though the location does not. When a message points at a spot that looks fine, the real problem is almost always slightly earlier — an unclosed bracket or a missing comma on a previous line, which the parser only notices when it reaches something unexpected.
For turning JSON into other shapes, JSON ↔ CSV handles flat tabular data and JSON to TypeScript generates interfaces from a sample payload — useful for pinning down a response you have just explored here. More are grouped on the developer tools page.