Most JSON arrives broken, so the error message matters more than the indentation
Indenting valid JSON is a solved problem. Every language ships it in one line. What sends people to a formatter is a 4,000 character response on a single line that a parser has rejected, with a message like Unexpected token } in JSON at position 2847 and no way to work out where position 2847 sits.
That is the job this page is built around. Paste the document and it reformats in place, so there is no second box to copy back out of. Underneath, a bar reports one of three things: the document is fine, the document is fine but has a repeated key, or the document is broken at line 42 column 17 because a comma follows the last property.
Why one editing surface rather than two panes
The two pane layout comes from diff tools, where you genuinely need to see two versions. Formatting has one version. Splitting it in half gives each side a narrow column, forces a copy step at the end, and leaves you editing on the left while reading the result on the right.
Here the document reformats where it sits. Fix the flagged line, watch the bar go green, press Copy. The full width also matters more than it sounds, since deeply nested API responses run wide at 8 space indentation and horizontal scrolling is the fastest way to lose your place in a structure.
What the checker names, and what a browser tells you instead
The page parses JSON with its own scanner rather than handing the text to JSON.parse. Built-in parsers stop at the first problem and describe it in terms of the grammar, which is accurate and close to useless when you are looking at somebody else's export. A scanner that knows the mistakes people actually make is able to name the cause.
| What is wrong | What a browser reports | What this page reports |
|---|---|---|
{'id': 1} | Unexpected token ' at position 1 | Property names must use double quotes |
{"a": 1,} | Unexpected token } at position 8 | Trailing comma before the closing brace |
{"a": 1 "b": 2} | Unexpected string at position 8 | Expected a comma or a closing brace after this value |
{"ok": True} | Unexpected token T at position 7 | Python capitalises its booleans, JSON does not |
{"n": 007} | Unexpected number at position 7 | Numbers cannot carry a leading zero |
{"p": "C:\Users"} | Bad escaped character at position 10 | Unknown escape sequence, a Windows path needs each backslash doubled |
Every message comes with a line and column, the offending line printed with a marker under the exact character, and a button that drops your cursor there.
Position numbers are the part that wastes the most time. A character offset is meaningless in a minified document, and it shifts the moment you edit anything above it. Line and column survive editing and match what your editor shows you when the same file fails in a build step.
Repeated keys are valid JSON and still a bug
An object holding the same key twice parses without complaint in every language. RFC 8259 calls the behaviour undefined, so implementations pick their own answer. JavaScript and Python keep the last one. Some Go and Java setups keep the first. A few parsers raise an error.
That disagreement is a genuine source of production bugs. A config file with "timeout" set twice behaves one way in the service that reads it with Jackson and another way in the deploy script that reads it with Python, and neither logs anything unusual.
The bar turns amber and names the repeated key when it finds one. Formatting keeps the last value, which matches what JavaScript and Python do, so the output is what most readers were already seeing.
Long numbers survive the round trip
This is the failure people find hardest to believe. Run a Twitter or Discord snowflake ID through most browser based formatters and the digits change:
{"id": 1234567890123456789} becomes {"id": 1234567890123456800}
Nothing went wrong with the formatter. JSON.parse turns every number into a JavaScript double, doubles carry about 15 digits of precision, and 19 digit IDs do not fit. The value is already wrong before any formatting happens, and a formatter built on parse then stringify hands you the damaged version back.
The scanner here keeps the original digits of every number as text and writes them back untouched. Long IDs, high precision decimals, 1E+5, -0.0: all of them come out the way they went in. Numbers are the one place where a formatter is able to quietly corrupt data, so it is worth checking whichever tool you use with a 19 digit integer.
Choosing an indent, and why sorting keys is a decision
Two spaces is the default here because it is what prettier, npm and most JavaScript projects emit, and because deep nesting stays on screen. Four suits Python and C# codebases. Tabs are the right answer when the file lands in a repository where a reader controls their own width. Eight exists for the rare structure that is easier to read when the levels are unmistakable.
Sorting keys alphabetically is off by default, and it deserves more thought than a formatting toggle usually gets. Sorting makes two API responses diffable, since a field that moved position no longer shows as a change. It also destroys meaningful ordering. Configuration files often put name and version first on purpose, and an alphabetical rewrite buries them. Sort when you are comparing, leave it alone when you are committing.
The tree is for finding paths, not for reading
The Tree tab renders the parsed document as collapsible rows with a count against every object and array. Two levels open by default and deeper branches build only when expanded, so a 5 MB response does not stall the tab.
Its real use is the path. Click any row and the dotted path lands on your clipboard, ready for a jq filter, a JSONPath expression or a line of application code. Keys that are not plain identifiers come out bracketed and quoted, so $.users[0]["content-type"] pastes in correctly rather than breaking at the hyphen.
What runs where
Everything happens in the page. There is no upload, no API call and no request in the formatting path, which is the only sane arrangement given what people paste into JSON tools. API responses carry session tokens, config files carry connection strings, and a support export carries other people's personal data. None of it should cross a network to gain some whitespace.
The practical consequence is that the browser tab is the limit. Documents up to a few megabytes format instantly. Past 300 KB the syntax colouring turns itself off, since keeping an overlay in step with every keystroke on a large document is what makes these pages stutter. Validation, formatting and the tree keep working.
Where this tool stops
- NoIt will not repair broken JSON. It names the mistake and puts your cursor on it, and you make the edit. Guessing at intent is how a missing quote turns into a silently wrong document. JSON Fixer takes the repair approach if you want it.
- NoOne document at a time. JSON Lines and NDJSON files hold one record per line, and the checker says so rather than reporting a syntax error. Wrap the records in brackets first, or use JSON to JSON Lines for the other direction.
- NoComments and trailing commas stay errors. JSON5 and JSONC are separate formats. Accepting their syntax here would pass files that fail in whatever reads them next.
- NoNo schema validation. Structure is checked, meaning is not. A missing required field or a string where a number belongs parses cleanly and always will.
- NoMulti gigabyte files belong in
jqor a streaming parser. A browser tab holds the whole document in memory, and mobile browsers drop background tabs long before a large file finishes. - YesKey order is preserved unless you ask for sorting, number text is preserved exactly, and string escapes are normalised to the shortest valid form.
