How to validate an api response is well-formed json
- Step 1Capture the real response body — From Postman/Insomnia 'Copy as text', or browser DevTools → Network → the request → Response tab → copy. Capture the body exactly as the server sent it, including any leading/trailing characters — those are often the culprit. Save it to a
.jsonfile. - Step 2Drop the response file and Validate — Drag the saved body onto the dropzone and click Validate. It runs in your browser; the response — tokens and PII included — is never uploaded. Free tier handles bodies up to 2 MB.
- Step 3Read the verdict — Green 'Valid JSON' means your client's
r.json()will succeed. Red 'Invalid JSON' shows the parser message and character position —position 0almost always means the body isn't JSON at all (HTML, redirect, empty). - Step 4Inspect the field names on success — Use the pretty-printed 'Parsed output' to read the actual field names, nesting depth, and value types. Compare against your expected contract by eye — this is where you spot a
userIdthat's now a string instead of a number, or a field that moved one level deeper. - Step 5Turn the body into a checkable contract — For real structural validation, paste a known-good response into json-schema-generator to infer a JSON Schema, then validate future responses against it with ajv in your test suite. This validator confirms parseability; the schema confirms shape.
- Step 6Diff two captures when a contract drifts — When an integration breaks after an upstream change, capture the old and new responses and compare them in json-diff to see exactly which fields were added, removed, or retyped.
What 'invalid' usually means for an API response
Mapping the V8 parser message to the most likely upstream cause, verified against JSON.parse. These are the failures r.json() throws in your client.
| Parser message | Position | Most likely cause |
|---|---|---|
Unexpected token < … is not valid JSON | 0 | Server returned HTML (error page, login redirect, WAF block) with a JSON content-type |
Unexpected end of JSON input | end | Body truncated — stream cut, gzip mismatch, or Content-Length lie |
Unexpected non-whitespace character after JSON | after the value | Trailing junk — a proxy banner, debug line, or two responses concatenated |
Unexpected token ' … is not valid JSON | varies | Body is actually JS object literal / single-quoted (a JSONP or mock, not JSON) |
Expected property name … | early | Trailing comma or unquoted key in a hand-written mock/fixture |
| Valid JSON | — | Body parses — now check fields against your contract by eye or with a schema |
Syntax check vs contract check — pick the right tool
This validator answers 'is it JSON?'. Contract testing answers 'is it the right JSON?'. They're different jobs.
| Question | This validator | Sibling tool / approach |
|---|---|---|
Will r.json() throw on this body? | Yes — pass/fail + position | — |
| Are all required fields present and correctly typed? | No — read the parsed output by eye | json-schema-generator + ajv |
| Which fields changed between two API versions? | No diffing | json-diff |
| Is a deeply nested response easy to navigate? | Shows pretty output | json-tree-viewer |
| Are there duplicate keys in the response? | No — V8 keeps the last silently | Review manually |
JSON tool size limits by tier
Per-file limits. Response validation is single-file on every tier.
| Tier | Max body size | Batch files |
|---|---|---|
| Free | 2 MB | 1 |
| Pro | 100 MB | 10 |
| Developer | 5 GB | unlimited |
Cookbook
Real API response failures and what the validator tells you. Tokens and PII anonymised; bodies abbreviated.
API 'returned JSON' but it's actually an HTML 502
ExampleThe gateway timed out and returned an HTML error page, but kept the upstream Content-Type: application/json header — so your client called r.json() and got a position-0 token error. The body starts with <, which is the dead giveaway.
Captured body: <!DOCTYPE html> <html><head><title>502 Bad Gateway</title></head>... Validator output: Invalid JSON Unexpected token '<', "<!DOCTYPE "... is not valid JSON Fix in client: check response.ok / content-type before r.json(); the upstream, not your parser, is the problem.
Truncated streamed response
ExampleA large paginated response was cut off when the connection dropped, leaving an object with no closing braces. JSON.parse runs out of input at the end of the file.
Captured body (tail):
{ "items": [ { "id": 1 }, { "id": 2 }, { "id":
Validator output:
Invalid JSON
Unexpected end of JSON input
Fix: the body is incomplete — retry the request, check
Content-Length vs bytes received, or disable a buffering proxy.JSONP / mock with single quotes
ExampleA teammate handed over a 'sample response' copied from a JS file — it's a JavaScript object literal with single quotes, not JSON. Your contract test parsed it locally because someone eval'd it, but real JSON tooling rejects it.
Captured 'response':
{ 'status': 'ok', 'data': [ 'a', 'b' ] }
Validator output:
Invalid JSON
Expected property name or '}' in JSON at position 2
Fix: regenerate a real response, or normalise to JSON with
json-format-fixer before using it as a fixture.Valid JSON, but the type quietly changed
ExampleThe body parses fine — the contract break is a field that switched from number to string (id: 42 → id: "42"). Syntax validation can't catch this; reading the pretty output (or diffing against a known-good capture) does.
Validator output:
Valid JSON (612 B)
Parsed output (excerpt):
{
"id": "42", <- was a number last week
"active": true
}
Next step: regenerate the schema (json-schema-generator) and let
ajv flag id: integer vs string in CI, or diff vs the old capture.Error-response contract check
ExampleYou validate both the success and the error body. The 4xx body is also valid JSON, so you can generate a separate schema for it and route validation by status code.
Success body → Valid JSON { "data": { ... } }
Error body → Valid JSON { "error": { "code": "RATE_LIMIT", "retryAfter": 30 } }
Both parse, so generate two schemas (success / error) and in tests:
status 2xx -> validate against success schema
status 4xx -> validate against error schemaErrors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
Response is an HTML error/redirect page
RejectedA gateway 5xx page, login redirect, or WAF block returned with a JSON content-type fails at position 0 with Unexpected token '<'. This is the most common 'the API broke my integration' case and it has nothing to do with your parsing code — check response.ok and the actual Content-Type before calling r.json().
Truncated / incomplete body
RejectedA body cut off mid-object (dropped connection, buffering proxy, gzip mismatch) fails with Unexpected end of JSON input. The fix is on the transport side — retry, verify Content-Length against received bytes — not in the parser.
Trailing data after the JSON value
RejectedA debug banner, log line, or a second concatenated response after the body causes Unexpected non-whitespace character after JSON. The validator parses one top-level value and treats anything after it as an error, exactly like r.json().
Valid JSON but a field was renamed or retyped
By design — not flaggedSyntax validation can't see contract drift. {"userId":"42"} is perfectly valid JSON even if your code expects userId to be a number. Read the pretty output, or for automated detection generate a schema with json-schema-generator and validate with ajv, or diff captures with json-diff.
Duplicate keys in the response
By design — not flaggedIf a misbehaving API emits {"id":1,"id":2}, it parses and V8 keeps the last value (2). This validator does not warn about duplicate keys, and neither do r.json() or jq. If you suspect this, inspect the raw body manually — the parsed output already collapsed the duplicate.
Empty body on a 204 / 304
RejectedA 204 No Content or 304 Not Modified has an empty body, which fails with Unexpected end of JSON input. That's expected — your client should not call r.json() on those statuses. Guard with a status check before parsing.
Top-level array or scalar response
SupportedSome endpoints return a bare array ([ {...}, {...} ]) or even a bare scalar (true, 42). All are valid JSON and validate green. If your client assumed an object wrapper, the body is still valid — the mismatch is a contract issue, not a syntax error.
BOM-prefixed response body
SupportedA response saved through a Windows tool may gain a leading U+FEFF BOM. The validator trims it before parsing (U+FEFF is whitespace in ECMAScript), so it passes. Be aware some server-side parsers don't strip it — a BOM that's fine here can still break a stricter consumer.
Non-finite numbers from a buggy serializer
RejectedAn upstream service that serialised NaN/Infinity (e.g. an unguarded division) produces invalid JSON — V8 rejects Unexpected token 'N'/'I'. Fix the producer to emit null or a sentinel; the validator can only flag it.
Body larger than the 2 MB free limit
BlockedFree tier caps bodies at 2 MB. Most API responses are far smaller. A multi-megabyte response usually means an unpaginated list endpoint — paginate it, or validate the full body on Pro (100 MB) / Developer (5 GB).
Frequently asked questions
Does this validate the response against my contract / schema?
No. It validates syntax only — whether the body parses as JSON. It will not tell you a field is missing, renamed, or retyped. For contract validation, infer a schema from a known-good response with the JSON Schema Generator and check future responses with ajv in your tests; this tool confirms the body is parseable in the first place.
Will its verdict match what my client's r.json() does?
Yes. The tool uses V8 JSON.parse, the same engine behind fetch().then(r => r.json()) and JSON.parse(await r.text()). If it passes here, r.json() succeeds; if it fails here with a given message, r.json() throws the same message.
Why does my body fail at 'position 0'?
Position 0 means the very first character isn't valid JSON. The overwhelmingly common cause is an HTML body (Unexpected token '<') — a gateway error page or login redirect returned with a JSON content-type. Check response.ok and the real content-type before parsing.
Is my response data uploaded anywhere?
No. Parsing runs entirely in your browser. Tokens, PII, and internal fields in the response never reach a JAD Apps server. You can verify in DevTools — clicking Validate fires no network request.
How do I validate both success and error responses?
Validate each separately — both are usually valid JSON. Then generate a separate schema for each shape (one from a 2xx body, one from a 4xx body) and route validation by HTTP status in your tests: 2xx against the success schema, 4xx against the error schema.
Can I validate streaming / chunked responses?
Only the fully assembled body. Capture the complete response (let DevTools or your client finish buffering), then validate. A partially captured stream will correctly fail with Unexpected end of JSON input — which is itself a useful signal that the stream was cut.
Does it detect a duplicate key in the response?
No. {"a":1,"a":2} parses and V8 keeps the last value. Neither this tool nor r.json() nor jq flags duplicate keys. Inspect the raw body by hand if you suspect a duplicate; the parsed output will already show only the surviving value.
My response is a bare array — is that valid?
Yes. RFC 8259 allows an array (or even a bare scalar) as the top-level value, so it validates green. If your consumer assumed a wrapping object, that's a contract expectation to fix on your side, not a JSON error.
What is consumer-driven contract testing, and does this do it?
Consumer-driven contract testing (e.g. Pact) has the consumer publish the response shape it expects, and the provider proves it satisfies every consumer's contract. This validator does not implement contracts — it only confirms a body is parseable JSON. Use it as the first, cheapest gate before contract tooling runs.
Why does the parsed output get truncated on screen?
The pretty-printed preview is capped at 5,000 characters for performance. Validation is computed over the entire body, and the Copy button copies the full re-serialised JSON — the truncation is display-only.
Can I paste the response instead of saving a file?
The tool reads a file you drop or select. Save the captured body to a .json file (DevTools and Postman both let you save the response) and drop it in. This keeps large responses out of the clipboard and preserves bytes exactly.
The body parses but my integration still breaks — what next?
Then it's a structural/contract issue, not a syntax one. Diff the working capture against the broken one in json-diff to see what changed, or generate a schema and run ajv to pinpoint the offending field and type. This tool has done its job by confirming the bytes are valid JSON.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.