JSON Formatter

Paste JSON, get it back indented and readable, or squeezed onto one line. When it will not parse you are told the line, the column and the character, with a caret pointing at it. Everything happens in this tab, and a 20 MB file is a normal Tuesday rather than a spinner.

Want to browse the structure instead of reading it? Open the JSON viewer

Why I built another one of these

FreeFormatter, which plenty of people had bookmarked for exactly this, has shut down. Most of what is left was built when a large JSON file meant a couple of hundred kilobytes. Hand those pages a few megabytes of API response and the tab hangs, or you hit a size limit with an upgrade button attached. Three things are different here.

  • Size is not the interesting part. 100 MB is the ceiling, and there is no row cap, no metering and no account. A 20 MB document of 130,000 records formats in roughly a tenth of a second on my laptop.
  • The error tells you where. Not "invalid JSON", not a red border. A line number, a column number, the text of that line and a caret under the character that broke it.
  • Your data stays in the tab. No upload endpoint exists on this page. The JSON people need to read is usually an API response with a token in it, or a customer record, or a webhook body from a payment provider. That is not something to hand to a stranger's server for a bit of indentation.

The jobs are always the same. Read a response that came back on one line. Diff two config files that were saved with different indentation. Shrink a payload before it goes into a request or a build artefact. Check whether the file a colleague hand-edited at 6pm is still valid. Turn a log line into something you can actually read.

Worked example: both directions

The Sample button loads this, which is what a record looks like after it has been through two systems and a text editor:

{"id":42,   "name":"Ada Lovelace","active":true,
"scores":[10,7,   9],"address":{"city":"London","postcode":"NW1 4RY"},
   "tags":["math","engine"],"notes":null}

Format, two spaces, which is where the controls start:

{
  "id": 42,
  "name": "Ada Lovelace",
  "active": true,
  "scores": [
    10,
    7,
    9
  ],
  "address": {
    "city": "London",
    "postcode": "NW1 4RY"
  },
  "tags": [
    "math",
    "engine"
  ],
  "notes": null
}

Switch the output to Minify and the indent control disappears, because there is nothing left to indent:

{"id":42,"name":"Ada Lovelace","active":true,"scores":[10,7,9],"address":{"city":"London","postcode":"NW1 4RY"},"tags":["math","engine"],"notes":null}

Minify also reports what it saved, which on a sample this small is 161 B → 150 B (7% smaller). On real input the number is worth looking at: a 400-record array pretty-printed at 39 KB comes back as 23 KB, 43% smaller, and all of that is whitespace nobody was reading. Indentation is two spaces, four spaces or tabs, and the choice only exists in Format mode.

When it will not parse

A trailing comma, which is the most common way a hand-edited file breaks:

Invalid JSON at line 5, column 1: Expected double-quoted property name.
5 | }
  | ^

The harder case is a minified document, where everything is line 1 and a character offset on its own is useless. The line is windowed around the caret when it is long, so you get the neighbourhood rather than four thousand characters:

Invalid JSON at line 1, column 64: Expected ',' or '}' after property value.
1 | {"users":[{"id":1,"name":"Ada"},{"id":2,"name":"Grace"},{"id":3"name":"Alan"}],"total":3}
  |                                                                ^

Column 64 is the quote before "name" in the third user, and the missing comma is right in front of it. The first sentence carries the line and column on its own, so it still reads correctly if you copy it into a chat message where the caret loses its alignment. Tabs are printed as single spaces in the snippet so the caret lands where the column count says it does.

One honest limit: the location comes from the character offset the browser reports, and not every message carries one. A few phrasings, and Safari as a rule, say what went wrong without saying where. You get the reason on its own then, rather than a line number somebody invented.

JSON Lines is handled, differently in each mode

Log files, warehouse exports and language-model training sets are usually JSON Lines: one complete JSON value per line, no wrapping array, no commas between records. It is recognised from the content, so a .jsonl, .ndjson or a .json file that turns out to hold one record per line all work. The two modes then have to part ways, because an indented record spans several lines and JSON Lines does not allow that.

  • Minify keeps the format. Each record is minified on its own line, the file stays JSON Lines and the download is a .jsonl file. Minifying per record is exactly what the format asks for.
  • Format changes the shape, and says so. The records are collected into a pretty-printed JSON array and the download becomes .json, with a message explaining that JSON Lines could not survive the indentation. Pick Minify if you needed the original shape back.
  • A broken record is located in the file. One bad line among good ones is reported as a JSON Lines problem at that line, for instance Invalid JSON Lines at line 2, column 9, rather than as an offset into a document that was never meant to be read as one value.
  • The record count comes with it. The summary says how many records were read, which is a quick way to check that a truncated download really was truncated.

What formatting cannot preserve

Formatting is not a text transformation. The document is parsed into values and printed again, which is the only way to be sure the output is valid, and it means a handful of things do not come back byte for byte. No JSON formatter built this way can promise otherwise, so here is the list:

  • Keys that look like integers move to the front. {"b":1,"2":2,"a":3,"1":4} comes back as {"1":4,"2":2,"b":1,"a":3}. Non-negative integer keys are emitted first, in numeric order. Everything else keeps its original order.
  • Duplicate keys collapse. {"id":1,"id":2} becomes {"id":2}. The specification allows duplicates and says nothing about which one wins, so the last one does and the other is gone.
  • Very large integers lose precision. 9007199254740993 comes back as 9007199254740992. Anything past 2^53 is beyond what a JavaScript number can hold exactly, which is a language limit rather than a bug in this page. If a payload carries snowflake ids or 64-bit keys as raw numbers, keep the original.
  • Number spelling is normalised. 1.10 becomes 1.1 and 1e3 becomes 1000. The value is identical, the text is not.
  • Escapes are unescaped where they can be. café comes back as café and an escaped forward slash becomes a plain one. Both are the same string, and both are valid JSON.
  • Comments are not JSON. A tsconfig-style file with // in it is rejected, with the caret on the first slash.

If the file is evidence, a signed payload, or anything where the exact bytes matter, format a copy and keep the original. For everything else, this is what every JSON library in every language does to your document anyway.

Frequently Asked Questions

How large a JSON file can it format?

Up to 100 MB, with no metering and no sign-up. A 20 MB document of 130,000 records formats in about a tenth of a second on my laptop, which is the part most online formatters get wrong. The preview shows the first 200 lines to keep the page responsive, and Copy and Download hand you the whole thing.

Is my JSON sent to a server?

No. Parsing and printing both happen in your tab, and this page has no upload endpoint. That matters here more than on most converters, because the JSON people need to read is usually an API response with a token, a customer record or a webhook payload in it. Nothing is stored between visits.

Why did my key order change, and where did my duplicate key go?

Formatting parses the document and prints it again, and a parsed object follows JavaScript's own rules. Keys that look like non-negative integers are emitted first in numeric order, so {"b":1,"2":2,"a":3,"1":4} comes back as {"1":4,"2":2,"b":1,"a":3}. Repeated keys collapse to the last one, so {"id":1,"id":2} becomes {"id":2}. Both are legal JSON, and neither is reversible, so keep the original if it is evidence.

Can it handle comments, as in tsconfig.json?

No. JSON has no comments, and a file with them is JSONC, which this refuses with a pointer at the first slash: Invalid JSON at line 2, column 3: Expected property name or '}'. Strip the comments first if you only need the values. Do not paste the stripped version back over your tsconfig, since the tooling there is happy with comments and your colleagues will want them.

What does Minify do to a .jsonl file?

It keeps the shape. JSON Lines means one record per line, so minifying each record on its own line is exactly right, and the download stays a .jsonl file. Format cannot keep that shape, because an indented record spans several lines, so it collects the records into a pretty-printed JSON array and tells you it did.

Why does an error sometimes have no line number?

Because the browser did not report one. Most parse failures come back with a character offset, which is turned into a line, a column and a caret under the offending character. A few messages, and Safari more generally, name only what went wrong. In that case you get the reason on its own rather than a line number that was guessed at.

Format or minify your JSON

Free, no account, no upload, no size games. Paste it in, read it properly, copy it back out.

Back to the formatter