← Back to Blog

The JSON Formatter & Validator Guide: Syntax, Validation, Schema, and Large Files

Why JSON Became the Default

JSON did not win because it is the most elegant design. It won because it sits at the right point between "enough" and "simple". A comparison of the three mainstream interchange formats:

Dimension JSON XML YAML
Size Small (no redundant tags) Large (tag pairs) Smallest
Data types 6, enough Requires XSD Rich, with type inference
Comments ❌ Not supported ✅ ✅
Parse cost Very low (native) High (DOM/SAX) Medium (indentation-sensitive)
Hand-authoring Medium (brackets are error-prone) Low High
Home turf Web APIs, config, logs Enterprise/finance legacy K8s, CI, IaC

The takeaway: use JSON for high-frequency machine-to-machine exchange, and YAML for config files humans maintain by hand. That is exactly why Kubernetes manifests are YAML while its API traffic is JSON.

When you need to check data types and escaping rules on the fly, keep the JSON cheat sheet open in a second tab.

JSON Syntax: Where It Is Strict

The entire JSON grammar is only a handful of rules, but every one is hard — there is no error tolerance.

Six Hard Rules

  1. Keys must use double quotes: "key", never key or 'key'
  2. String values must use double quotes, not single quotes or backticks
  3. No trailing commas: no comma after the last element
  4. No comments: both // and /* */ cause a parse failure
  5. Encoding must be UTF-8 (mandated by RFC 8259)
  6. The top level may be an object, array, string, number, true/false, or null — not just an object

Six Data Types

Type Example Gotcha
string "hello" Double quotes required; escape control characters
number 42, 3.14, 1e10 No NaN, Infinity, leading zeros, or hex
boolean true / false Lowercase only
object {"a": 1} Keys must be strings
array [1, 2, 3] Element types may be mixed
null null Not undefined, not NULL

Valid vs Invalid

Snippet Verdict Why
{"a": 1} ✅ —
{a: 1} ❌ Key not quoted
{'a': 1} ❌ Single quotes
{"a": 1,} ❌ Trailing comma
{"a": 01} ❌ Leading zero
{"a": NaN} ❌ No such literal in JSON
{"a": undefined} ❌ No such type
[1, "2", null] ✅ Arrays may mix types

Formatting vs Minifying: Not an Aesthetic Question

How Much Minifying Saves

Stripping all indentation and newlines typically cuts 30–50% of the payload. Where key names repeat heavily (say ten thousand records of the same shape), gzip or Brotli widens the gap further. A 10 MB JSON can shed 3–5 MB of transfer — several seconds on a weak mobile connection.

When you need to minify, use a JSON minifier rather than a hand-rolled regex that strips whitespace; a regex will corrupt spaces inside string values.

Indentation Conventions

Style Where it is used
2 spaces Most common — the default in the JS/TS ecosystem (Prettier, ESLint)
4 spaces Python ecosystem, some Java projects
Tab Rare, and incompatible with YAML — not recommended

What matters is consistency within one repository. Inconsistency hurts more than the choice itself.

Two Counter-Intuitive Points

  • Formatting before signing breaks verification. HMAC, digital signatures, and JWTs are computed over the raw byte sequence; one extra space changes the result completely.
  • Do not print beautified large objects into production logs. Print the minified form and truncate by length, or log volume and write latency both run away.

Validation: Catch Errors Before Runtime

JSON.parse often throws nothing more than Unexpected token } in JSON at position 1234, which looks useless. Knowing the distribution lets you locate it in seconds.

The Five Most Common Errors

Error type Typical message Actual cause
Trailing comma Unexpected token } Extra comma after the last item
Single quotes Unexpected token ' Copied from a JS literal
Unclosed bracket Unexpected end of JSON input Missing } or ], reported at end of file
Comments Unexpected token / Someone wrote a // note
BOM Unexpected token at position 0 File starts with 

The Locating Heuristic

Look backwards from the reported position, not forwards. A parser only throws at the character it cannot continue from, so the typo is usually a few lines earlier. Concretely:

  • Error at the end → almost certainly an unclosed bracket; count pairs from the outside in
  • Error pointing at a comma → usually an extra trailing comma, not missing content
  • Error at position 0 → check BOM and encoding first
  • Error mid-file → look at the end of the previous line

Pasting the raw content into a JSON formatter is the fastest first move; most implementations mark the offending line and column directly.

JSON Schema: Turning Structure Into an Executable Contract

Validating "is this legal JSON" only settles syntax. Validating "are the fields and types right" requires JSON Schema.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["id", "email", "createdAt"],
  "additionalProperties": false,
  "properties": {
    "id": { "type": "string", "format": "uuid" },
    "email": { "type": "string", "format": "email" },
    "age": { "type": "integer", "minimum": 0, "maximum": 150 },
    "status": { "enum": ["active", "suspended", "deleted"] },
    "createdAt": { "type": "string", "format": "date-time" }
  }
}

Commonly Used Keywords

Keyword Purpose
type Type constraint; may be an array like ["string", "null"]
required List of mandatory fields
enum Enumerated allowed values
minimum / maximum Numeric range
minLength / maxLength String length
pattern Regex match (debug it with a regex tester)
additionalProperties Set to false to reject undeclared fields
format Semantic format (email/uri/uuid/date-time, etc.)

Inferring a Schema From Real Responses

Hand-written schemas drift from real data. The sturdier approach is to generate a first draft from an actual response and then tighten it by hand — that is what a JSON Schema generator is for. Afterwards, tighten additionalProperties and mark which fields may be null; only then is it a usable contract.

Running it in CI with ajv surfaces structural breakage from API changes before merge.

Large Files and Streaming

Memory Is Not "As Big As the File"

This is the most commonly misjudged point: JSON in memory after parsing typically occupies 3–10x the original text. Every key and value becomes a separate object or string with pointer and metadata overhead. A 100 MB JSON file can easily need 500 MB–1 GB of RAM for a naive JSON.parse.

File size Recommended approach
< 10 MB Just JSON.parse; no optimisation needed
10–100 MB Stream it, or process in batches
> 100 MB Streaming mandatory; consider newline-delimited JSONL
> 1 GB Switch to a columnar or binary format (Parquet, Arrow)

Streaming Parsers

Library Language Notes
simdjson C++ with bindings SIMD-accelerated; GB/s-class parsing
ijson Python Lazy iteration, emits as it reads
JSONStream Node.js Incremental extraction by JSONPath
serde_json StreamDeserializer Rust Zero-copy friendly

Extracting Fields With JSONPath

If you only need a few fields out of a large file, do not parse the whole tree — target them with a JSONPath expression and discard irrelevant branches as you go. You can tune the expression against real data with the JSONPath finder.

Comparing Structures: What Actually Changed

After changing config or upgrading an API, eyeballing two JSON documents almost always misses something. Compare the parsed structure instead of the text:

  • Added or removed keys → check required and compatibility
  • Changed values → check whether the type drifted ("1" vs 1)
  • Reordered arrays → decide whether order carries business meaning

JSON Diff gives you structured differences, far more reliable than textual line diffs, which drown in formatting noise.

Security: Four Real Risks When Parsing JSON

Risk Impact Fix
Parsing with eval() / new Function() Remote code execution, total compromise Always use JSON.parse
Prototype pollution Deep-merge mutates every object's prototype Strip __proto__/constructor/prototype before merging
Deep nesting stack overflow Recursive parser crashes; denial of service Enforce a depth limit before parsing
Large-integer precision loss Money and order ids silently become wrong Transmit fields above 2^53-1 as strings

Number two is the sneakiest:

const payload = JSON.parse('{"__proto__":{"isAdmin":true}}');
const config = {};
merge(config, payload);           // Dangerous: pollutes Object.prototype
console.log({}.isAdmin);          // true — every object in the app is affected

Number four is the most damaging in finance and ordering: 9007199254740993 parses to 9007199254740992, and nothing throws.

Task Cheat Sheet

Task Approach Tool
Read a one-line minified JSON Beautify with 2-space indent JSON formatter
Track down a parse error Paste it in and read the line/column JSON formatter
Shrink a payload Strip whitespace JSON minifier
Browse a deep structure Tree viewer JSON viewer
Diff two config revisions Structural diff JSON Diff
Infer a schema from a response Generate, then tighten JSON Schema generator
Pull fields from a big file JSONPath expression JSONPath finder
Debug a field regex Validate the pattern first Regex tester
Advertisement

Frequently Asked Questions

Can JSON strings use single quotes?

**No.** The JSON specification requires that keys and string values be wrapped in **double quotes**; single quotes, backticks, or unquoted keys are all invalid. This is the most common mistake when moving from JavaScript object literals — `{ name: 'Alice' }` is a valid JS object but throws immediately when parsed as JSON. Likewise, **no trailing commas**: `{ "a": 1, }` is legal JS but illegal JSON. **JSON is not YAML either**: just because YAML supports comments does not mean you can put `//` or `/* */` in JSON — a standard parser will reject it.

JSON parsing fails and the error only gives a line and column. How do I locate it fast?

**Look backwards from the reported position, not forwards.** A parser only throws when it hits **the first character it cannot continue from**, so the actual typo is usually a few lines **earlier**. Practical steps: 1. paste the raw string into a [JSON formatter](/json-formatter.html) — most will point at the offending line directly; 2. if the error is at the end of the file, it is almost always an **unclosed bracket** — count `{}` and `[]` pairs from the outside in; 3. if it points at a comma, you usually have an **extra trailing comma**, not missing content; 4. for Chinese content also check whether the file carries a **BOM** (a leading `\uFEFF` makes `JSON.parse` fail outright) and whether the encoding is UTF-8.

When should I minify JSON and when should I format it?

**Minify for transport, format for humans, and never mix the two.** API responses in production, config files, and message payloads should be minified (all indentation and newlines stripped) — measurements typically show **30–50%** smaller payloads. Beautify only for local debugging, code review, and log analysis. Two points that are easy to miss: **1. never format before signing** — digital signatures, HMAC, and JWTs are computed over **raw bytes**, and formatting changes the byte sequence so verification fails; **2. printing beautified large objects into production logs slows writes and blows through log quotas** — log the minified form and truncate by length. After minifying, use [JSON Diff](/json-diff.html) to confirm the parsed structure is unchanged.

What are the real security risks of parsing JSON from untrusted sources?

**Four, ordered by severity: 1. parsing with `eval()` or `new Function()`** — that executes the other party's string as code and is a total compromise; always use `JSON.parse`. **2. Prototype pollution** — `JSON.parse('{"__proto__":{"isAdmin":true}}')` yields an ordinary object carrying a `__proto__` key, and if it gets deep-merged (`Object.assign`, lodash `merge`) into a config it can mutate the prototype of every object; the fix is to strip `__proto__`, `constructor`, and `prototype` before merging. **3. Stack overflow from deep nesting** — a maliciously built hundred-thousand-level `[[[[...]]]]` blows the stack of recursive parsers, so enforce a depth limit up front. **4. Precision loss on huge numbers** — `JSON.parse` converts integers beyond `Number.MAX_SAFE_INTEGER` into floats, so money and order-id fields must be transmitted as strings.

← Back to Blog