JSON Formatter & Validator

Format, minify and validate JSON in your browser. This one also flags the two failures that never raise an error — integers past 253−1 that get rounded, and duplicate keys that silently overwrite each other.

Precision loss — this is silent, not an error.
  • 9007199254740993 becomes 9007199254740992
These exceed JavaScript's safe integer range (253−1). Send such values as strings, or parse with a big-number aware library.
Duplicate keys — the last one silently wins.
  • ok
JSON.parse keeps the final occurrence and discards the rest. Other parsers may keep the first, so the same document can mean different things in different languages.

The failures that do not raise an error

A syntax error is the easy case: the parser stops, points at a position, and you fix it. The expensive bugs are the ones where JSON parses cleanly and the data is wrong anyway. There are two of these and both are common enough that the tool above checks for them on every input.

Integers larger than 253−1

JSON itself places no limit on how large a number may be, but JSON.parse produces JavaScript doubles, which hold integers exactly only up to 9,007,199,254,740,991. Beyond that the value is rounded to whatever is representable, and no exception is raised:

JSON.parse('{"id": 9007199254740993}').id
// 9007199254740992          <- last digit changed

JSON.parse('{"v": 12345678901234567890}').v
// 12345678901234567000      <- last three digits gone

This bites hardest on identifiers, because a rounded ID still looks like a plausible ID. Database bigints, snowflake-style IDs and monetary amounts in minor units all belong in JSON strings rather than JSON numbers, converted deliberately where they are used. The scanner above reads the raw text rather than the parsed value, because once JSON.parse has returned, the original digits are gone.

Duplicate keys

RFC 8259 says object names should be unique but does not require it, and explicitly leaves parser behaviour undefined when they are not. JavaScript keeps the last occurrence:

JSON.parse('{"a": 1, "a": 2}').a
// 2      <- no error, first value discarded

Other implementations keep the first, and some raise. That means an identical document can carry different meaning depending on which language reads it — a genuine interoperability hazard rather than a style preference, and one that typically appears when two systems merge fragments of JSON by string concatenation.

The failures that do raise an error

Trailing commas are the most frequent, inherited from JavaScript and Python where the syntax is legal. JSON has no grammar production for them, so parsing stops with a position you can act on. Comments are likewise absent from the format; if you need either for a configuration file, reach for JSON5 or JSONC and convert before handing the result to a strict parser.

NaN and Infinity are also rejected on the way in — but note the asymmetry on the way out. JSON.stringify does not throw on them, it writes null instead, and it drops undefined values entirely rather than nulling them. A value can therefore leave your program as one thing and arrive as another with nothing logged.

Frequently asked questions

Why did my large number change after parsing JSON?

Because JSON.parse turns every number into a JavaScript double, which represents integers exactly only up to 2^53−1, or 9007199254740991. Past that, digits are rounded to the nearest representable value and nothing warns you. We measured it: 9007199254740993 comes back as 9007199254740992, and 12345678901234567890 becomes 12345678901234567000. The JSON specification itself sets no limit on numeric size, so the document is perfectly valid — the loss happens entirely in the parser. Anything that must survive intact, such as a database bigint, a Twitter-style snowflake ID or a financial amount in minor units, should travel as a JSON string and be converted deliberately at the edge. Note that JSON.parse's reviver cannot rescue you here: it receives the already-rounded number, not the original text, so the fix has to happen in the producer or in a big-number aware parser.

Are duplicate keys allowed in JSON?

The grammar permits them and JSON.parse accepts them without complaint, keeping the last occurrence and discarding the rest — we confirmed that {"a":1,"a":2} parses to a value of 2. RFC 8259 says names within an object should be unique but stops short of requiring it, and leaves the behaviour of a parser that meets a duplicate undefined. That is what makes this dangerous rather than merely untidy: some implementations keep the first occurrence instead, so the same document can mean two different things in two different languages. The tool above flags duplicates for exactly this reason, since no error will ever surface on its own. In practice they usually arrive one of two ways: fragments of JSON merged by string concatenation rather than by parsing, or a serialiser that writes a field twice from two different code paths.

Why is a trailing comma invalid in JSON?

Because JSON is a data interchange format rather than a programming language, and its grammar simply has no production for it. JavaScript and Python both tolerate a trailing comma in their own literal syntax, which is why the habit carries over and why this is one of the most frequent syntax errors. The parser reports it clearly — {"a":1,} raises "Expected double-quoted property name in JSON at position 7" — so unlike the precision and duplicate-key problems, this one fails loudly. If you want comments and trailing commas in a configuration file, use JSON5 or JSONC and convert before handing the result to a strict parser. Both add comments, trailing commas and unquoted keys; JSONC is what VS Code accepts in its settings files, which is why the habit spreads. Neither is JSON, so never send them over the wire to a consumer expecting RFC 8259.

Can JSON hold NaN or Infinity?

No. The grammar admits only decimal numbers, so NaN, Infinity and -Infinity are all syntax errors on the way in — JSON.parse('{"a":NaN}') throws. The subtle part is the way out: JSON.stringify does not throw on them, it silently substitutes null, so {a: NaN} serialises to {"a":null}. Undefined values disappear entirely rather than becoming null, so {a: undefined, b: 1} serialises to just {"b":1}. Both conversions are lossy and neither raises anything, which means a round trip through JSON can quietly turn a computed value into a missing field. A division that produced Infinity therefore reaches the consumer as an explicit null, indistinguishable from a value that was genuinely absent. If the distinction matters, check before serialising or supply a replacer function that makes the intent explicit.

Is anything I paste here sent to a server?

No. This page is a static file and all of the work happens in your browser through the built-in JSON.parse and JSON.stringify, plus a small scanner that reads the raw text to find precision loss and duplicate keys. There is no API call behind the buttons and no analytics event carrying your document, so the page keeps working with the network disconnected once loaded. That makes it safe to paste a real API response rather than a redacted one. The only network request the site makes at all is a cookie-less Cloudflare Web Analytics beacon that counts page views, and it carries the page URL rather than anything from the text area. Nothing is written to local storage either, so reloading the page clears whatever was there.

References

Guides

More browser-only tools at withuse.io/tools.