JSON Formatter

Paste a payload, get it laid out, syntax-highlighted and measured. When it's broken, the error tells you the line and column and shows you the offending bit — not just "Unexpected token".

Input Tab inserts two spaces
Output

          
Type
Nodes
Keys
Max depth
Size

JSON is stricter than JavaScript. Trailing commas, // comments and single-quoted strings are all valid in a JS object literal and all rejected by JSON.parse. If the parser is complaining about a character you can see nothing wrong with, it is almost always one of those three.

The caret is the engine's own offset

A browser's JSON.parse throws a message that names the character position and nothing else — Unexpected token } in JSON at position 412 — which is precise and useless in equal measure, because a character offset is not something anyone counts. The formatter reads that offset out of the message, counts the newlines before it to get a line and column, and prints the line itself with a caret underneath. Same parser, same refusal, but now the fault is a place on the page you can look at:

{
        "retries": 3,
        "backoff": "exponential",
      }
                              ^
      Unexpected token } in JSON at position 58
      line 4, column 1

That is a trailing comma on line three, and the caret lands on the brace that follows it. This is worth knowing because the reported position is where the grammar gave up, not where the mistake is. A missing closing brace three levels up surfaces as a complaint about the end of the file. An unescaped quote inside a string surfaces as a complaint about the character after it, because that is the first thing the parser sees that does not fit. Read the caret as "the grammar could not continue here" and work backwards from it.

Numbers that change while you read them

JavaScript has one numeric type: the IEEE 754 double, which holds integers exactly up to 9007199254740992 and approximates everything past it. The grammar has no such limit — it will happily carry a 19-digit integer — so a payload can contain a number that the parser accepts and then quietly rounds.

This is not a hypothetical. Snowflake identifiers from Discord and Twitter/X, Stripe object ids in their numeric form, and any database using a 64-bit primary key all sit above that boundary, and all of them change value on the way through a JSON parser. The document was valid, the parse raised no error, and the id you send back is a different id. Nothing in the tool can warn about it, because the number was already rounded by the time any code could look at it.

Identifiers belong in quotes. A value that will never be added, averaged or compared for size is text, and writing it as text is what keeps it intact. The change belongs at the producer, before the field is ever serialised — once a large integer has been through a JSON parser on the client, the original digits are gone and no formatting on this page can bring them back. If the payload is arriving from somewhere you do not control, treat the field as a string on receipt and convert it at the last moment, if at all.

A duplicate key that wins

RFC 8259 says names in an object should be unique and then declines to make it an error. Every parser therefore picks a house rule, and the near-universal one is last-wins: the second "id" replaces the first, and by the time the object exists there is no trace of the one that lost.

{ "id": 41, "state": "draft", "id": 42 }

      // parses without complaint, and the object is:
      { "id": 42, "state": "draft" }

A producer that merges two objects with an object spread hits this without meaning to, and the result is a field that is present, plausible and wrong. The formatter will not flag it, because it is not a syntax error and inventing a complaint about valid input would be worse than staying quiet — a tool that argues with the grammar is a tool you stop trusting. If a value looks stale, search the raw text for the key name and see whether it appears more than once.

What a diff needs before it is stable

Two payloads carrying the same data can produce completely different text, and key order is the reason. The grammar calls an object an unordered collection, and most languages preserve insertion order anyway, so the same object built by two code paths lands in two different orders. Comparing those two strings directly shows every line as changed.

Sorting keys alphabetically at every level, leaving arrays alone, removes that whole class of noise — and leaving arrays alone is the important half, because array order is data. A list of ids sorted to make a diff quieter is a different list, and any consumer that relied on the original order now has a bug that no test will catch until something depends on position.

Sorting is for comparison, not for storage. A response that goes over the network is better left in whatever order the serialiser produced, since gzip handles the repetition either way and the original order is closer to the code that made it. Sort, compare, then send the unsorted version. For the comparison itself, the Diff Checker reads two documents side by side and marks the lines that actually differ; if the payload also needs to become a spreadsheet, JSON to CSV flattens it into columns.

Reference

The JSON grammar, token by token

TokenWhat it isValid JSON?
{ } Object — an unordered collection of name and value pairs. Names are strings, and a repeated name is not a syntax error. Yes
[ ] Array — an ordered list of values. The order is part of the data, and one array can hold mixed types at any depth. Yes
"text" String — double quotes only. A quote, a backslash or a control character inside one has to be written as a backslash escape. Yes
12 Number — an optional minus sign, digits, an optional fraction and exponent. A leading zero, a leading +, a bare .5, NaN and Infinity are all outside the grammar. Yes
true, false Boolean — lowercase, and the only two words of that type. True and TRUE are not JSON. Yes
null Null — a type of its own, not a missing key and not the four-character string "null". Yes
'text' Single-quoted string — the most common failure of the lot. Valid in JavaScript, valid in a Python dict, never in JSON. No
{ a: 1 } Unquoted name — a name is a string in double quotes even when it looks like a plain identifier. No
[1, 2, ] Trailing comma — a comma sits between two values, so the last entry in an object or array carries none after it. No
// note Comment — there is no comment syntax at all. Annotations have to travel in a string field or in a companion file. No
undefined, NaN, Infinity Values JavaScript has and the grammar does not. JSON.stringify writes NaN and Infinity as null, and drops an undefined value from an object. No

Every row above the first No is the whole of RFC 8259: those tokens are the format. The rows below it are what other languages and config formats accept, and what a JSON parser stops dead on.

Read the verdict column as a claim about the grammar, not about the JavaScript in the next tab. Every token with a Yes beside it is in RFC 8259; everything below it is something a JavaScript engine accepts in source code, where an object literal is code being evaluated rather than a document being read.

That strictness is the point. JSON exists so a service in Go and a client in Python agree on what a payload means, and each convenience the grammar allows is one more thing two implementations can disagree about: comments become dialects, trailing commas become generators that emit them, NaN becomes a value other languages read differently. Keeping the grammar small is what makes a correct parser cheap in every language, and why JSON.parse rejects text a console accepts — one reads a document, the other evaluates code.

Nearly-JSON turns up often in configuration. tsconfig.json and the VS Code settings accept comments, and JSON5 adds single quotes and unquoted keys deliberately. They are separate formats with their own parsers, and this tool will not read them: the error it reports is the one your production code would throw. If one program reads the file, use the superset and its parser; if it crosses a network boundary, convert it to strict JSON first.

Questions

JSON, answered plainly

Why does it say "Unexpected token" with no explanation?

Because that's the raw message from the browser's engine — the tool wraps it with the line, column and a snippet so it's actually usable. Nine times out of ten the cause is a trailing comma before a closing brace, or a single-quoted string where double quotes are required.

Should I minify before sending over the network?

Only if you're not compressing. Any gzip or Brotli layer removes whitespace far more effectively than stripping it by hand, and keeps the payload readable in logs. If your API doesn't compress, minifying is worth the few percent — but it's never worth losing readability in a debug session.

What does "Sort keys" actually do?

It rebuilds the structure with object keys in alphabetical order, at every level. Arrays keep their original order because order is meaningful there. Useful for producing stable diffs — two payloads that differ only in key order will compare as identical.

Is there a size limit?

Live validation pauses above about 400,000 characters so typing stays responsive — press Format to parse anyway. Beyond a few megabytes the browser tab itself becomes the bottleneck, not this tool.

Can a JSON document be a bare string or number?

Yes, and JSON.parse accepts it — the grammar allows any value at the top level, not only an object or an array. It is usually a bug, though, because a response that is a bare scalar leaves the producer no room to add a field later without breaking every client. If you don't control the producer, check for null before you treat the value as an object: null parses without error and then throws on the first property read.

How do I parse a config file that has comments in it?

Not with JSON.parse, and this tool won't either — a double-slash comment is outside the grammar, which is why the error points at the first slash. tsconfig.json, the VS Code settings and a good deal of JavaScript tooling use JSONC, a superset that adds comments and trailing commas, and they read it with a parser that admits as much. If you need one of those files, use a JSONC or JSON5 parser rather than stripping comment lines with a regular expression, because a double slash inside a URL string will be cut in half. Convert it to plain JSON before it crosses a network.

Should timestamps be strings or numbers?

Strings, in ISO 8601, in UTC — 2024-01-31T09:15:00Z. An epoch number is smaller and sorts correctly, but it carries neither a time zone nor a unit, so a payload holding 1706692500 does not say whether that is seconds or milliseconds. Strings come back unchanged from every language's JSON library, stay readable in logs, and sort lexicographically while they are all UTC. If you need both, send the string and derive the number on the client.