JSON formatter
Processed in your browser · nothing is uploaded
Formats JSON with the indent you choose, 2 spaces, 4, or a tab, and says exactly where it broke if it will not parse: line and column rather than a shrug. The same box minifies, sorts keys, flattens to dotted paths and back, strips empty values, reads JSON Lines into a single array, and evaluates a JSONPath expression against the document.
How to use the json formatter
Almost every rejection comes down to one of three things, and all three are valid JavaScript, which is why they slip through. A trailing comma after the last element is fine in JS and illegal in JSON. Single quotes are fine in JS and illegal in JSON: strings must use double quotes. Unquoted keys are fine in JS and illegal in JSON. Two rarer causes are worth knowing: a byte order mark at the start of a file, which is invisible and breaks the parse on the very first character, and NaN or Infinity, which JavaScript produces happily and JSON cannot represent at all.
Everything past formatting is the same parse followed by a different way of writing the document out, which is why it all lives on one page. Minifying strips whitespace outside strings, typically 15 to 30 percent of a formatted document, though gzip has usually taken that saving already; it earns its keep where nothing compresses, such as a value in a database column or a string embedded in a page. Because the document is parsed and re-emitted rather than scanned for spaces, numbers are normalised on the way through: 1.0 comes back as 1 and 1e3 as 1000, and an integer above 9007199254740991 loses its last digits. Not a quirk of this tool but of every JavaScript consumer your JSON will meet, and the reason large IDs belong in strings.
Sorting keys is a diff tool in disguise. Key order means nothing to the standard and nothing to any parser, and everything to a text comparison, so two documents that differ only in field order produce a diff full of noise until both are sorted. Sorting is alphabetical at every level and leaves arrays alone, because array order is data rather than formatting. Two surprises: keys that look like non-negative integers always come first in numeric order, so "2" precedes "10", which is a JavaScript object rule rather than a choice made here; and duplicate keys do not survive the parse, so only the last one is left.
Flattening turns nesting into dotted paths, user.address.city, with array positions in brackets, which is what almost every flat-key system expects: translation files, feature flags, environment config, analytics payloads, a spreadsheet column. Unflattening rebuilds the nesting from those paths, reading a bracketed number as an array position and a name as an object key. The round trip is lossless for ordinary data and lossy in exactly one place: a key that already contains a dot cannot be told apart from nesting.
Removing empty values strips null, empty strings and empty containers, recursively, so an object left empty because its contents went is removed too. It deliberately keeps false and 0, which are values rather than absences. Be careful where an API treats an explicit null as "clear this field": dropping it says nothing at all instead, which is not the same instruction. And JSON Lines, one complete JSON value per line and also called NDJSON, becomes a single array, with the line number named when a row will not parse. A file rejected here is almost always a pretty-printed object spanning several lines, which is the one thing the format forbids.
JSONPath is the one operation that reads the document instead of rewriting it. The expression goes in the field under the input and the matching values come back as an array, in document order: $..author gathers every author at any depth, $.store.book[?(@.price < 9)] returns the books under nine. Dot and bracket notation both work, * is a wildcard, .. is recursive descent, a negative index counts back from the end and [0:2] is a slice. Filters are parsed rather than run through eval, which is how the original 2007 proposal worked and turns an untrusted expression into arbitrary code. The comparisons are ==, !=, <, <=, > and >=, plus a bare @.field as a presence test. A filter that does not begin with @.field is refused with a message; one that begins correctly and then asks for something unsupported — a regex match, a negation, two conditions joined with && — falls back to the presence test on that first field instead of erroring, and quietly returns everything that carries it. A filter matching far more than you expected is nearly always that. Two things catch people out: the syntax was only standardised in 2024, as RFC 9535, so implementations written before it genuinely disagree about descent and filters; and recursive descent really does search every depth, which is why $..price in the sample document finds the bicycle as well as the books.
What people use it for
- Making an unreadable API response readable
- Stripping whitespace before storing JSON in a database column
- Sorting keys so two documents diff cleanly
- Flattening nested JSON into dotted paths for a spreadsheet
- Rebuilding nesting from a flat set of dotted keys
- Clearing nulls and empty fields out of a payload
- Turning an NDJSON export into a single array
- Pulling every value at one path out of an API response
Questions
The three usual causes are a trailing comma after the last item, single quotes instead of double, and unquoted keys. All three are valid JavaScript and none is valid JSON.
No. JSON has no comment syntax. JSON5 and JSONC add them, but neither is JSON and a standard parser rejects both.
Usually a byte order mark. An invisible character some editors write at the start of a UTF-8 file. Save without a BOM.
Not to the standard or to any parser. It matters a great deal to a text diff, which is why sorting both documents before comparing them helps. The sort here is dictionary order, so apple comes before Apple before zebra; a byte-order sort such as Python’s sort_keys puts every capital first, and the two will not agree.
No. Array order is part of the data; sorting it would change the meaning rather than the formatting. Nested objects are sorted at every level.
Because keys that look like non-negative integers always come first, in numeric order. That is a JavaScript object rule, not a choice made here, so "2" precedes "10".
Only the last survives, because the document is parsed before it is written out again. That changes the data, so check first.
Not the values, and it saves 15 to 30 percent. Only whitespace outside strings is removed; whitespace inside a string is part of the value and stays, and key names are never shortened. Numbers are rewritten, because the document is parsed and re-emitted rather than scanned: 1.0 becomes 1, 1e3 becomes 1000, and an integer above 2^53 loses its last digits, as it would in any JavaScript consumer. Over a gzipped connection the real saving is far smaller than the raw figure, so turn compression on first and minify on top of it only for a large payload.
To get a nested document into something that only understands flat keys. Translation files, feature flags, environment variables, analytics payloads and spreadsheet columns are all flat maps. For an actual CSV, the JSON to CSV tool is the better route, because a spreadsheet wants rows and a header rather than dotted keys.
Dots for object keys and square brackets for array indices, so a.b[0].c, which is the convention most config systems use. The round trip is lossless for ordinary data, and empty arrays and objects are preserved explicitly so they survive it. A key that already contains a dot is the one case it cannot: on the way back it splits, and there is no way to tell it from real nesting.
It returns as an array index. {"counts.1":5} unflattens to {"counts":[null,5]}, because a numeric segment can only mean a position, and a missing index is filled with null rather than guessed at.
No. Those are meaningful values. Only null, empty strings and empty containers go, and it is recursive, so an object left empty because its contents went is removed as well. A string of spaces is not empty here; trimming values is a separate decision.
The gap closes: [1, null, 3] becomes [1, 3], so everything after it shifts down an index, which matters if anything downstream reads that array by position. It can change the meaning elsewhere too. Where an API treats an explicit null as "clear this field", stripping it says nothing at all instead.
One complete JSON value per line, with no line breaks inside a value; also called NDJSON. It exists because it streams: a consumer can act on line one without reading the file, and a writer can append without rewriting the end. A file rejected here is almost always a pretty-printed object spanning several lines, and the error names the line that failed.
Yes. Pick JSONPath, put the expression in the field under the document, and the matching values come back as an array in document order. The chips beneath the field are worked examples against the sample.
Standardised only in 2024, as RFC 9535, so implementations written before it genuinely disagree about descent and filters. Filters here are parsed rather than evaluated as JavaScript, which is how the original 2007 proposal worked and what turns an untrusted expression into arbitrary code. That leaves ==, !=, <, <=, > and >=, plus a bare @.field as a presence test. A filter not starting with @.field is refused with a message, while one that starts correctly and then uses an unsupported operator, a negation or && falls back to the presence test on that first field and matches everything carrying it.
Recursive descent searches every depth, not just the array you had in mind. In the sample document the bicycle has a price as well.
No. Parsing and formatting happen in your browser, which matters when the JSON is an API response with real data in it.