How to Validate and Debug JSON Like a Pro
A bad JSON payload is one of the few bugs that has a single, deterministic root cause: one byte in the input is wrong. The hard part is finding that byte inside a 200 KB API response, a 4 MB config file, or a stream of NDJSON chunks arriving on a websocket. This post is the workflow we use internally — the same set of moves our engineers run when a customer pastes a payload that broke their pipeline.
The 90-second triage
Paste the offending text into this site's JSON Formatter. The bottom of the page reports line and column. Read those numbers first; do not jump to the parser's error text until you have them in your head, because the parser's position is often off-by-one or measured in code units instead of characters, and you will otherwise chase a non-existent ghost.
Once you know the line and column, work backwards from that point. The actual mistake is almost always one of three forms:
- An unescaped character: a stray double quote, a literal newline, or a bare backslash inside a string.
- A structural mistake: a missing closing brace, an extra comma after the last element, an empty separator like
,,between two elements. - An encoding mistake: UTF-8 BOM, Latin-1 bytes that look like ASCII, a surrogate pair that was split.
The eight production error categories
Every "valid JSON that won't parse" report we've ever investigated falls into exactly one of these eight categories. The order is approximately the order of how often they appear in real bug reports.
-
Trailing comma.
{"a": 1, "b": 2,}— the comma after2is invalid. JSON forbids trailing commas in both arrays and objects; JavaScript accepts them in array/object literals; many YAML exporters output JSON with trailing commas because YAML itself allows them. The fix is a one-line regex:s/,\s*([}\]])/$1/g. -
Single-quoted strings or unquoted keys.
{'a': 1}and{a: 1}are both invalid. JSON requires double quotes everywhere — keys, string values, both. The fix is to standardise on double quotes; a regex that survives apostrophes inside strings is annoying to write, so use the JSON Formatter's tree view to spot offenders quickly. -
Comments. JSON does not support
//or/* */comments. JSON5 does; if your source has comments, you either accept JSON5 (and lose compatibility with strict downstream parsers) or you strip comments upstream with regex. -
Unicode escapes.
\x41,\e, and lone\are all invalid. JSON accepts exactly nine escape sequences:\",\\,\/,\b,\f,\n,\r,\t, and\uXXXX(four hex digits, no more, no less). Anything else is rejected. -
Numbers with leading zeros.
0123is invalid;0.123is valid;"0123"is valid (it is a string). JSON forbids leading zeros on integers for the same reason C/Java/Python did historically:0123looks octal, and JSON has no octal. -
NaN, Infinity, undefined. These are not part of JSON at all. A producer emitting
NaNas a bare token is non-conformant. JSON.stringify refuses to emit them — it substitutesnull, which is the documented escape hatch. -
Encoding mistakes. A UTF-8 file with a BOM, a Latin-1 file mislabelled UTF-8, a Windows-1252 file from an SQL Server export, or a CSV file from a Japanese accounting system in Shift-JIS — all of these look like "invalid JSON characters" to a strict UTF-8 parser. The first move is always to detect and re-encode upstream; see the CSV encoding guide for the workflow.
-
Truncated input. Network timeouts, partial file writes, and broken pipes all leave you with valid-looking JSON that stops abruptly in the middle. The error reports a position very close to the end of the buffer. Fix the producer, not the parser.
When the JSON parses but the app still fails
This is the most common case in real systems, and the hardest to debug, because every tool says "valid JSON". The bug is semantic, not syntactic. JSON Diff is the right tool here: paste your payload and a known-working reference side by side, and the structural view will list the exact paths that differ.
The patterns we see over and over:
- String vs number mismatch. The API expects
"42"and gets42, or vice versa. JSON cannot distinguish "the user's age as a string" from "the user's age as a number" — only the consumer can. Encode money, IDs, and any value that must round-trip exactly as strings; encode counts, sizes, and any value that participates in arithmetic as numbers. - Null vs missing.
{"name": null}and{}are semantically different in almost every consumer. If your serializer omits null fields by default,{"name": null}becomes{}and the consumer'sobj.name ?? defaultValuepicks updefaultValueinstead ofnull. Pick one convention and stick to it; document it; test the other side. - Case-sensitive keys.
"Username"vs"username"is a one-character difference that JSON cannot resolve. Server-side case-insensitive matching hides the bug at one layer and surfaces it at the next. - Date format drift. Some producers emit ISO 8601 strings (
"2026-08-06T12:34:56Z"); others emit epoch seconds (1754483696); others emit epoch milliseconds (1754483696000); and one or two poorly-written producers emit Microsoft JSON Date format ("\\/Date(1754483696000)\\/"). Document the format on both ends and validate it on the receiving end.
Build a debugging pipeline that does this for you
You will paste the same character into the search box three times a week for as long as you maintain a system that touches JSON. Build the muscle memory:
- First stop: paste into the JSON Formatter. Read the line and column.
- Second stop: if the parser accepts the input but the program fails, paste both your payload and a known-good reference into JSON Diff and read the structural diff.
- Third stop: if the input is from a third-party API, capture the raw bytes (not the parsed value) in your logging so the next time the bug shows up you have a reproduction.
Once the muscle memory is in place, a bad JSON payload becomes a 90-second fix instead of an afternoon. Tools: JSON Formatter, JSON Diff, and the JSON reference guide for the underlying specification.
Related Tools
JSON Formatter
Format, minify, validate and beautify JSON with inline error highlighting.
JSON Diff
Compare two JSON documents side-by-side with line-level highlighting and key sorting.
JSON → String
Escape JSON into a string literal suitable for embedding into source code (double quotes and backslashes escaped).