Polyatic JSON Formatter

JSON Formatter & Validator

Pretty-print, validate and minify JSON live, with clear line/column errors. Nothing is uploaded.

Paste or type JSON

Indent applies when pretty-printing. Turn on Minify for the smallest one-line output. Sort keys sorts object keys at every depth (even inside arrays) by plain code-unit order — Z before a, not locale-aware; array order is data and is never sorted. JSONC input additionally accepts JSONC-style // and /* */ comments plus trailing commas (stripped before parsing — output stays strict JSON). Single quotes and unquoted keys are never accepted. Infer schema replaces the output with a JSON Schema draft-07 describing your current sample — required is inferred from the keys present in this sample; edit as needed. The Nested / Flatten / Unflatten switch collapses nested JSON into a single-level object of dotted paths (a.b.c) with bracketed array indices (a[0]) — or rebuilds the nesting from such a map; it round-trips losslessly and refuses (never corrupts) a source key containing . [ or ].

Waiting for JSON…
Output


        

Runs entirely in your browser — your JSON never leaves your device. Parsing and formatting use the built-in JSON engine and a small script you can read; there are no network requests and no analytics on this tool.

Supported: $ root · .key · ['key'] · [n] (negatives ok) · [start:end] slice · * wildcard · .. recursive descent. Not supported: filter ?() and script expressions — those return an honest "unsupported" message, never a wrong result.

Matches

    

What this tool does

Paste any JSON into the box on the left and it is parsed and re-rendered on the right the instant you stop typing — no button to press. If the JSON is valid you get a tidy, colour-coded, indented version (or a minified one), plus a count of how many keys and array elements it contains and its size in bytes. If it is invalid you get a red status and, wherever the browser gives us a character position, the exact line and column of the problem with a short excerpt of the offending text.

Worked example: fixing broken JSON

Here is a snippet that looks fine at a glance but fails to parse. Paste it in and the status turns red:

{
  'name': "widget",
  "price": 9.99,
  "tags": ["new", "sale",],
  "inStock": True
}

There are three separate defects, and JSON.parse only reports the first one it hits. Fix them in order and re-paste after each to watch the error move:

  1. Single-quoted key 'name' → JSON keys and strings must use double quotes: "name".
  2. Trailing comma after "sale", → delete the comma before the closing ]: ["new", "sale"].
  3. Python-style boolean True → JSON booleans are lowercase: true (and null, not None).

The corrected document is {"name":"widget","price":9.99,"tags":["new","sale"],"inStock":true}, which validates and pretty-prints cleanly. The lesson: the reported position marks where parsing failed, which is usually the character just after the real mistake — so look immediately before the caret, not at it.

Common JSON syntax errors

Error you'll seeUsual causeFix
Unexpected token '''Single quotes on a key or stringUse double quotes everywhere
Unexpected token ']' or '}'Trailing comma after the last itemRemove the comma before the bracket
Unexpected token 'T' / 'N'True/False/None from PythonUse true, false, null
Unexpected token in JSONUnquoted key, e.g. {name: 1}Quote the key: {"name": 1}
Unexpected end of JSON inputMissing closing } or ]Balance every opening bracket
Bad control character in stringA raw tab or newline inside a stringEscape it as \t or \n
Unexpected non-whitespace after JSONTwo top-level values, or a // commentWrap in an array, or turn on JSONC input

Formatting versus minifying

These are two views of the same data. Pretty-printing adds newlines and indentation (2 spaces, 4 spaces, or a tab per level) so a human can follow the nesting — the file gets bigger but becomes readable and diff-friendly. Minifying removes every optional space and newline to produce the smallest valid JSON, which is what you send over a network or paste into source code. For the corrected example above, the minified form is 67 bytes while the 2-space pretty-printed form is 97 bytes — identical data, about 45% more whitespace. Toggle Minify freely; both round-trip losslessly. Sort keys additionally rebuilds every object with its keys in plain code-unit order at every depth, including objects nested inside arrays — so {"b":1,"Z":2,"a":3} becomes {"Z":2,"a":3,"b":1}, because in UTF-16 code units every uppercase letter (Z = 90) precedes every lowercase one (a = 97). That is not locale-aware alphabetical sorting, and deliberately so: code-unit order is identical on every browser and OS, which is exactly what you want for a stable, diff-friendly output. The reorder is purely cosmetic, since JSON objects are unordered. Array order is never touched, because arrays are ordered and that order is part of the data.

JSONC input: comments and trailing commas, repaired to strict JSON

Plenty of real-world "JSON" files are actually JSONC — JSON with comments — because that is what VS Code's settings.json, TypeScript's tsconfig.json and many build tools read. Strict parsers reject them. Turn on the JSONC input chip and this tool repairs such input before parsing: // line comments, /* block */ comments, and trailing commas (a comma sitting directly before a closing } or ]) are removed, a visible "Repaired" notice appears so nothing happens silently, and the formatted output on the right is always strict RFC 8259 JSON — the tool never writes comments back out.

The stripper is a character-by-character state machine, not a find-and-replace, so comment-lookalikes inside strings survive byte-for-byte: {"homepage": "http://example.com"} keeps its //, {"note": "/* not a comment */"} keeps its stars, and escaped quotes like "a\"b//c" are tracked correctly so a string never ends early. An unterminated /* produces an honest error naming the line where the comment opened — never silently mangled output.

Still not tolerated, even with the toggle on: single-quoted strings, unquoted keys, NaN/Infinity, hex numbers, and Python-style True/None. Those fail with a clear error, because accepting them would quietly change what your data means. This is deliberately JSONC-level tolerance — comments and trailing commas only — not a full loose-JSON parser. One more honesty note: a doubled comma like [1,,] is an elided element, not a trailing comma, so it stays an error.

Infer a JSON Schema (draft-07) from a sample

Turn on the Infer schema chip and the output pane is replaced by a JSON Schema that describes the document you pasted. It always declares "$schema": "http://json-schema.org/draft-07/schema#" — this tool only ever emits draft-07 and never claims a different draft. The schema itself re-parses as strict JSON, so you can pipe it straight into a validator. This is a fast way to bootstrap a contract for an API response you already have a sample of, rather than writing the schema by hand.

The one thing you must understand is what a single sample can and cannot tell you, because that is exactly where naive "schema generators" quietly lie:

  • Optionality is a guess, so we make it explicit. For an object, every key present in your sample is listed in required. One sample cannot know a field is optional, so required is inferred from the keys present in this sample; edit as needed — delete the ones that are actually optional. An object with no keys emits no required at all.
  • Number vs integer is pinned, not guessed. A JSON number with no fractional part is typed integer; anything with a fraction or exponent that leaves a fraction is number. JSON does not distinguish 2 from 2.0, so 2.0 is reported as integer and 2.5 as number. If you need 2 to validate as a float, widen the type by hand.
  • No format keywords are invented. A string that looks like an email or an ISO date is typed only {"type":"string"}. Guessing "format":"email" from one value would be a fabrication, so the tool stays type-only on purpose — add the format keyword yourself when you know it applies.
  • The union rule for arrays. An array becomes {"type":"array","items":…}. If every element is an object, items is a single merged object schema: its properties are the union of all keys seen, and its required is only the keys present in every element — so a key missing from some elements is correctly left optional. Otherwise the distinct element schemas are collected and, if they differ, combined with anyOf. An empty array yields a bare {"type":"array"} with no items, because nothing in the sample reveals the element type — the honest answer is to constrain nothing.
  • null becomes {"type":"null"}, which validates only null. If a field is nullable-but-usually-set, you will want to widen it (for example to {"type":["string","null"]}) by hand.

In short: the inferred schema is a faithful, minimal description of the one document you gave it — a strong first draft, not a finished contract. Everything the sample cannot prove (optional fields, nullable unions, string formats, integer-vs-float intent) is left for you to tighten, and the tool tells you plainly where those gaps are rather than hiding them behind a confident guess.

Flatten and unflatten to dot-notation

The Flatten mode turns a nested document into a single-level object whose keys are the full path to each leaf value. It is what you want when a system expects flat key-value pairs — form fields like address[city], a .env-style config, a spreadsheet column layout, or the dot-path column names some NoSQL and analytics tools use. Unflatten is the exact inverse: hand it a flat map and it rebuilds the nesting.

The path grammar is fixed and deliberately unambiguous:

  • Object keys join with dots. {"user":{"name":"Ada"}} flattens to {"user.name":"Ada"}.
  • Array indices use square brackets. {"tags":["json","tools"]} flattens to {"tags[0]":"json","tags[1]":"tools"}, and nesting composes: {"rows":[{"id":1}]} becomes {"rows[0].id":1}.

Splitting the two forms — dots for members, brackets for indices — is the whole point, because it keeps a number-like object key distinct from an array position. {"a":{"0":"x"}} flattens to {"a.0":"x"} (a member named 0), while {"a":["x"]} flattens to {"a[0]":"x"} (index 0). A tool that wrote a.0 for both would rebuild one of them as the wrong container; here unflatten reads the brackets and puts each value back where it came from.

Round-trip and the cases we refuse

unflatten(flatten(x)) returns a document deep-equal to x for every supported input, and the flattened output always re-parses as strict JSON. Two situations are genuine ambiguities, and rather than guess we handle them openly:

  • A key that already contains a delimiter is refused, not mangled. If a source object key literally contains ., [ or ] — say {"a.b":1} — there is no honest way to tell its flattened path apart from the nested {"a":{"b":1}}. Flatten stops with a clear error naming the offending key instead of silently corrupting your data. Keep that document nested, or rename the key.
  • Empty containers are preserved where they can be. A nested empty object or array keeps its key as a leaf: {"meta":{}} flattens to {"meta":{}} and rebuilds intact. The one honest asymmetry: a top-level empty object and a top-level empty array both flatten to {} (there is no key to hang them on), so unflattening {} yields {} — a top-level {} round-trips, a top-level [] does not.
  • Sparse arrays are not reconstructed. Flatten never produces a gap — it walks arrays 0…n-1 — but if you paste a hand-made flat map into Unflatten that skips an index (a[0] and a[2] with no a[1]), the missing slot fills with null rather than becoming a true hole. That is the honest limit of a flat map: absence and null look the same.

Common mistakes

  • JSON is not JavaScript. It looks like a JS object literal but forbids trailing commas, comments, unquoted keys, single quotes, and undefined. If you copied it out of a .js file, expect to clean it up.
  • Keys must be double-quoted strings. {width: 20} is valid JavaScript but invalid JSON; it must be {"width": 20}.
  • No trailing commas. The last element of an object or array cannot be followed by a comma — the single most common cause of a red status.
  • Booleans and null are lowercase. true/false/null, never True, TRUE or None.

Honest limits

Everything here runs on your own device: the page loads no third-party scripts and makes no network calls, so a config file, an API token or a customer record you paste stays in the tab and is gone when you close it. That is deliberate — many online JSON tools POST your paste to a server to format it. Because it validates against the real JSON standard (RFC 8259), it is a strict validator by default: it will correctly reject trailing commas and comments rather than silently accepting them. The one deliberate, clearly-labelled exception is the opt-in JSONC input toggle described above — and even that repairs the input to strict JSON rather than loosening what the output means. Very large documents (tens of megabytes) can briefly freeze the tab, because parsing and rendering happen synchronously on the main thread. And numbers keep only the precision JSON.parse gives them, so integers beyond 2^53 (about 9 quadrillion) lose their last digits — exactly as they would in any JavaScript program. If you need those preserved, keep large ids as strings.

Want the theory? Our JSON Explained guide is a first-principles walkthrough of the six value types, the strictness rules (double quotes, no trailing commas, no comments), how JSON differs from JavaScript object literals, and what each parse error really means.