GlobalKit

JSON Formatter

Validate JSON against RFC 8259, pretty-print it with 2 or 4 spaces or tabs, or minify it. Errors come with line and column. Nothing is uploaded.

Up to 500,000 characters. Your JSON never leaves your device.

Orders object keys alphabetically by Unicode code point, at every level.

Writes characters such as ş, ü or emoji as \uXXXX sequences.

Paste JSON in the input box to validate and format it.

Everything runs in your browser; nothing is uploaded.

Paste JSON in the input box and the tool validates it, shows it in the indentation you choose and lists what it contains. Everything is processed on your device; the text is never uploaded.

How it works

The tool checks the text against the grammar of RFC 8259 (the same grammar as ECMA-404) with its own parser. It does not call JSON.parse, for two reasons. The error messages of JSON.parse differ between engines and give no reliable line and column, and JSON.parse converts every number to a double, which silently changes some values. The parser keeps its own stack instead of using recursion, so deep nesting cannot overflow the call stack, and nothing you paste is ever passed to eval.

What the grammar requires:

If the text is valid, the output uses your options. Indentation can be 2 spaces, 4 spaces, a tab, or none at all (minified, no whitespace outside strings). Empty objects and arrays are written as {} and []. Object members keep their source order unless you switch on Sort keys, which orders keys by Unicode code point at every level; arrays are never reordered. Escape non-ASCII characters writes every character from U+0080 upward as a \uXXXX escape, with an emoji written as a surrogate pair of two escapes.

Numbers are copied as raw text, so 0.10 stays 0.10, 1.0E+2 stays 1.0E+2 and integers above 2^53 are not rounded. Strings are re-escaped minimally: \/ and \u0041 come out as / and A, and a lone surrogate escape that cannot be written as UTF-8 is kept as an escape.

Below the result the tool lists statistics: objects, arrays, keys, string values (keys are not counted), numbers, booleans, nulls, maximum nesting depth and the UTF-8 size of input and output in bytes. A scalar document has depth 0 and [] has depth 1.

Examples

Error positions. For invalid JSON the tool shows the line, the column, the offending text and the line itself with a caret. These positions come from the tool's own tests:

InputCodeLineColumnFound
[1,2,]TRAILING_COMMA15,
['a']SINGLE_QUOTES12'
[01]INVALID_NUMBER1201
[.5]INVALID_NUMBER12.5
[NaN]INVALID_NUMBER12NaN
[True]INVALID_LITERAL12True
[1 "a"]UNEXPECTED_TOKEN14"
[1,2UNEXPECTED_END15(end of input)
[1]]EXTRA_DATA_AFTER_VALUE14]

Across several lines the position follows the line breaks. In this document the error is on line 3, column 8, because tru is not a literal (the tool reports INVALID_LITERAL, found tru):

{
  "a": 1,
  "b": tru
}

A comment on its own line, such as // c at the start of line 3, is reported as COMMENT at line 3, column 1. {"a":1} // x gives COMMENT at line 1, column 9. For ["😀", x] the column is 8, not 7, because the emoji occupies two UTF-16 code units.

Turkish text and escapes. The input {"şehir":"İstanbul","ad":"Çağrı","sayı":3} with Sort keys on and minify gives:

{"ad":"Çağrı","sayı":3,"şehir":"İstanbul"}

ş (U+015F) sorts after s and y because the order is by code point, not by the Turkish alphabet. With Escape non-ASCII characters on, the same output becomes:

{"ad":"\u00c7a\u011fr\u0131","say\u0131":3,"\u015fehir":"\u0130stanbul"}

Code point order also puts uppercase before lowercase: {"b":1,"a":{"z":1,"y":2},"B":3} is sorted to {"B":3,"a":{"y":2,"z":1},"b":1}.

Statistics. [true,false,null,1,2.5,"a","b",{"k":"v"},[]] has 2 booleans, 1 null, 2 numbers, 3 string values (a, b and v), 1 object with 1 key and 2 arrays, with a maximum depth of 2.

Large numbers. In {"id": 9007199254740993, "ok": 1234567890123456} the first integer is reported as one that JSON.parse would round to 9007199254740992. The second has 16 digits but fits a double exactly, so it is not counted. A number such as 1E400 is written back unchanged, although JSON.parse turns it into Infinity.

Things to watch for

Frequently asked questions

Last updated: 10/11/2026

Related tools