# JSON Format & Validate

> Validate JSON with exact error locations, pretty-print or minify it, sort keys, and find duplicate keys.

Category: Text & Data · Page: https://util.caruana.me/tools/json/

## API, MCP and A2A

### Format and validate JSON (`json_format`)

`GET|POST https://util.caruana.me/api/json/format` · MCP tool `json_format` · A2A skill `json_format` · [reference](https://util.caruana.me/reference/json_format/)

Parses `input` strictly as RFC 8259 JSON and reports the first syntax error with line, column, offset, JSON Pointer and an excerpt; messages name the usual mistakes (trailing commas, single quotes, unquoted keys, comments, NaN, leading zeros) and say how to fix them. Invalid JSON is a successful response with `valid: false`, so check `valid`. For valid JSON it returns the document re-indented or minified, optionally with keys sorted, plus statistics. Only whitespace (and, with `sort_keys`, member order) changes: strings and numbers are copied exactly as written, so `1.0`, `1e5` and 20-digit integers survive. Duplicate keys, which JSON.parse silently collapses, are listed with JSON Pointers; the output keeps the last value at the first position, as JSON.parse does. Nesting deeper than 500 levels is rejected.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `input` | string (≤ 500000 chars) | yes | The JSON text. Send it as a string field (`{"input": "{\"a\":1}"}`) or as a raw `text/plain` POST body. A byte order mark is reported as an error. |
| `indent` | 2 \| 4 \| tab \| none | no | JSON output indentation: `2` or `4` spaces, `tab`, or `none` for minified output (no whitespace at all). |
| `sort_keys` | boolean | no | Sort object keys recursively, by UTF-16 code units (the order RFC 8785 uses). Array order never changes. |

Privacy: The server receives the JSON text, processes it in memory inside the request, and returns the result. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. The server makes no network requests. For confidential documents use the page, which does the same work in your browser and sends nothing.

### Lint JSON (`json_lint`)

`GET|POST https://util.caruana.me/api/json/lint` · MCP tool `json_lint` · A2A skill `json_lint` · [reference](https://util.caruana.me/reference/json_lint/)

Parses `input` as strict RFC 8259 JSON and runs the lint rules: `syntax`, `duplicate-keys`, `number-precision`, `string-encoding`, `indent-style`, `indent`, `trailing-spaces`, `final-newline`, `line-endings`, `byte-order-mark`, `line-length`. Every finding has a rule ID, severity, message, line, column and offset. Invalid JSON is a 200 response with `valid: false`. Use `json_format` for the formatted output and statistics.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `input` | string (≤ 500000 chars) | yes | JSON text, up to 500,000 characters. |
| `indent` | 2 \| 4 \| tab \| none | no | Expected indentation unit for the `indent` rule. |
| `max_line_length` | integer (0–10000) | no | Limit for the `line-length` rule; 0 disables it. |

Privacy: The server receives the JSON text, lints it in memory and returns the findings. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded.


## User guide

The page is a small studio. The studio works like a desktop window. Its menu bar has **File** (open, load the sample, download, clear), **Edit** (format, copy), **View** (switch views, show the options inline, full screen), **Settings** (**Options…** opens the options in an overlay) and **Help** (jumps to these sections, and lists the keyboard shortcuts). The **full-screen** icon sits at the right of the same row: two arrows pointing out make the studio fill the window, and arrows pointing in (or **Esc**) bring it back.

Your **Input** is never overwritten: every result has its own view, so you can always flip back.

1. **Paste, type or open a file** in the **Input** view, or choose **File → Load sample** (the **File** menu also has **Open file…** and **Download input**). The text is checked as you type. The status bar shows whether it is valid, a summary (type, keys, arrays, depth, size) and the number of findings. The line numbers on the left are coloured where there is a problem: red for errors, amber for warnings, blue for information.
2. **Edit → Format** (or **Ctrl+Enter**) shows the result in the **Formatted** view, pretty-printed with your chosen indent and, if ticked, sorted keys. **Edit → Minify** shows the most compact form. Use **Copy**, **Download**, or **Use as input** to make the result your new input.
3. **Tree** shows the document as a collapsible tree, like a JSON viewer. Click a node to see its type, size and value, and its path in three notations, ready to copy: JSONPath (`$.items[0].id`), JSON Pointer (`/items/0/id`) and JMESPath (`items[0].id`). **Filter** finds keys and values and opens the branches that contain them. **Expand all** and **Collapse all** do what they say.
4. **Problems** lists every finding with its line, column and rule. Click one to jump to that spot in the input.
5. For large documents, the **full-screen** icon (right end of the menu bar) makes the whole studio fill the window. Press **Esc** or the icon again to return.

**Settings → Options…** sets the indent (2 spaces, 4 spaces or tab), key sorting, and the line-length limit for the `line-length` rule (0 turns it off). The lint rules are listed in full under **Lint rules** below.

**Duplicate keys.** `{"id": 1, "id": 2}` is accepted by most parsers, but they disagree on which value wins, and `JSON.parse` silently keeps the last one. The page lists every repeated key with its JSON Pointer and where both copies are. The formatted output keeps the last value, at the position of the first, which is what `JSON.parse` does.

**Formatting never changes your data.** Only whitespace changes (and the order of keys, if you sort them). Strings and numbers are copied exactly as written: `1.0` stays `1.0`, `"é"` keeps its escape, and a 20-digit ID is not rounded. Tools that re-serialise through `JSON.parse` lose all three.

**Large numbers.** An integer beyond 9,007,199,254,740,991 (2^53 − 1) is valid JSON, but JavaScript and many other languages read it as the nearest double, so `12345678901234567890` becomes `12345678901234567000`. The page warns about such numbers; if every digit matters, send them as strings.

## Specification

**Grammar.** RFC 8259 (identical to ECMA-404), with no extensions. A JSON text is exactly one value, surrounded by optional whitespace.

| Rule                                                    | Behaviour                                                                                                                                  |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Whitespace                                              | Only space, tab, LF and CR. A no-break space or other Unicode space is an error.                                                           |
| Byte order mark                                         | A leading U+FEFF is an error (RFC 8259 §8.1: senders must not add one).                                                                    |
| Empty input                                             | An error.                                                                                                                                  |
| Top-level value                                         | Any value, including a bare string, number, `true`, `false` or `null`.                                                                     |
| Strings                                                 | Double quotes only. Characters U+0000–U+001F must be escaped. Escapes: `\" \\ \/ \b \f \n \r \t \uXXXX`. Any other escape is an error.     |
| Unpaired surrogates                                     | A `\uD800`–`\uDFFF` escape that is not part of a pair is accepted, with a warning: it is not valid Unicode.                                |
| Numbers                                                 | `-? (0 \| [1-9][0-9]*) (. [0-9]+)? ([eE] [+-]? [0-9]+)?`. No `+` sign, leading zeros, bare `.5` or `1.`, hexadecimal, `NaN` or `Infinity`. |
| Literals                                                | `true`, `false`, `null`, lowercase only.                                                                                                   |
| Comments, trailing commas, single quotes, unquoted keys | Errors. There is no JSON5 or "relaxed" mode: anything accepted here is accepted by every conforming parser.                                |
| Nesting                                                 | At most 500 levels of objects and arrays.                                                                                                  |

**Error location.** Only the first error is reported, because after a syntax error the rest of the text has no reliable structure.

- `line` and `column` are 1-based; `offset` is 0-based from the start of the input.
- Columns and offsets count Unicode code points, so an emoji counts as one character. LF, CRLF and a lone CR each end a line.
- `pointer` is the RFC 6901 JSON Pointer of the value that was being read, for example `/items/3/name`; `""` is the root.
- `excerpt` is the offending line, cut to about 40 characters either side of the error (cuts are marked `…`), followed by a line with `^` under the error. Tabs and control characters are shown as one-character placeholders so the caret lines up.

**Duplicate keys.** Keys are compared after decoding escapes, so `"a"` and `"a"` are duplicates. Each repeat is listed with the JSON Pointer of the member, its line, column and offset, and the line and column of the first occurrence. RFC 8259 says names _should_ be unique, so duplicates do not make the syntax invalid, and `json_format` accepts them. The linter (the studio and `json_lint`) reports them as errors, as eslint-plugin-jsonc and jsonlint's strict mode do: the status bar then says the JSON _parses_ but has lint errors. The formatted output keeps the last value at the position of the first occurrence.

**Warnings.** At most 100 are listed, then one line with the number left out.

- An integer (no fraction or exponent) whose magnitude exceeds 2^53 − 1, with the value a double-based parser would read.
- A number that overflows a double (for example `1e400`), which JavaScript reads as `Infinity`.
- An unpaired surrogate escape.

**Formatting.**

- **Indentation:** `2` or `4` spaces, or a tab, per level. Each member and element is on its own line; a colon is followed by one space. Empty objects and arrays print as `{}` and `[]`.
- **Minify:** no whitespace at all between tokens.
- **Content:** strings, keys and numbers are copied byte for byte from the input. The output has no trailing newline.
- **Sort keys:** recursive, by UTF-16 code units (`"B"` before `"a"` before `"é"`), the order RFC 8785 (JSON Canonicalization Scheme) uses. Arrays keep their order. Because numbers and string escapes are kept as written, sorted and minified output is not full RFC 8785 canonical form.

**Statistics** (valid input only).

| Field                             | Meaning                                                                         |
| --------------------------------- | ------------------------------------------------------------------------------- |
| Depth                             | 0 for a scalar; 1 for a flat object or array; one more per level of nesting.    |
| Objects, arrays                   | Number of each, at every level.                                                 |
| Members                           | Object members, duplicates included.                                            |
| Strings, numbers, booleans, nulls | Values of each type. Keys are not counted as strings.                           |
| Input size, output size           | UTF-8 bytes. An unpaired surrogate counts as 3 bytes (it is encoded as U+FFFD). |

**API.** `POST /api/json/lint` returns every lint finding. `POST /api/json/format` with `input` (the JSON text, as a string field or a raw `text/plain` body), `indent` (`2`, `4`, `tab` or `none`) and `sort_keys`. Invalid JSON is a 200 response with `valid: false` and the `error` object, not an HTTP error. `input` may be at most 500,000 characters; the page has no limit.
