JSON Beyond the Basics: Validation, JSON Schema and Querying with JSONPath
What "valid JSON" actually means, the errors that break parsers, how JSON Schema enforces structure, and how JSONPath lets you query a payload without writing a script.
Valid JSON is stricter than JavaScript
JSON looks like a JavaScript object literal but the grammar is deliberately narrow. No comments. No trailing commas. Keys must be double-quoted strings. No single quotes anywhere. No undefined, no NaN, no Infinity, no hex numbers, no leading + or leading zeros. The top level may be any value (an object, array, string, number, true, false or null) — a point many validators get wrong.
- The most common breakage: a trailing comma after the last property, copied from JavaScript source.
- Second: unescaped control characters or a raw newline inside a string — must be
\n. - Third: numbers like
.5or01that JavaScript accepts and JSON rejects. - Fourth: smart quotes
“ ”pasted from a document instead of straight".
Precision is the silent one: JSON numbers are arbitrary-precision by spec, but most parsers map them to IEEE-754 doubles, so a 19-digit ID round-trips with its last digits changed. APIs that issue large integer IDs should send them as strings for this reason.
JSON Schema: making structure a contract
Valid syntax says nothing about meaning — {"age": "twelve"} is perfectly valid JSON. JSON Schema describes what a document must contain: types, required keys, enums, ranges, string formats, array bounds, and nested object shapes. It turns "I hope the API sends what I expect" into a check you can run in CI, generate types from, and hand to another team as documentation.
{
"type": "object",
"required": ["id", "email"],
"properties": {
"id": { "type": "string", "pattern": "^usr_[a-z0-9]{12}$" },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 150 },
"roles": { "type": "array", "items": { "enum": ["admin", "editor", "viewer"] } }
},
"additionalProperties": false
}additionalProperties: false is the line most people forget and most people need — without it, a misspelled key (emial) validates happily and the bug surfaces in production.
JSONPath: querying without a script
When you need "every order total over 100 for customers in Germany" out of a 40 MB export, you do not want to write a program — you want a query. JSONPath is to JSON what XPath is to XML: $.orders[?(@.total > 100 && @.customer.country == "DE")].id. $ is the root, . and [] descend, * wildcards, .. searches recursively, and ?() filters. It is the fastest way to answer questions about an unfamiliar payload during debugging.
Pretty vs minified, and when it matters
Whitespace is insignificant in JSON, so minifying is always safe and typically saves 20–40% before gzip (far less after). Pretty-print for humans — logs, config files, fixtures in a repo — and minify for the wire. One real hazard: some tools "pretty-print" by re-serializing, which can reorder keys, change number formatting (1.0 → 1) or drop precision. If byte-exactness matters (signed payloads, checksums), never re-serialize — only reformat whitespace.