How to Validate JSON in JavaScript
From a one-line try/catch to structural validation — the correct patterns, and the mistakes that look like they work.
The Short Answer
JavaScript has no JSON.isValid(). The idiomatic way to validate JSON is to try parsing it and see whether it throws:
function isValidJson(text) {
try {
JSON.parse(text);
return true;
} catch {
return false;
}
}This looks like a workaround and is in fact the correct approach. Validity is defined as "a conforming parser accepts it", so running a conforming parser is not an approximation of the answer — it is the answer. JSON.parse is implemented in native code and is faster than anything you could write in JavaScript to check the same thing.
Why Not a Regular Expression
A recurring suggestion is to test JSON with a regex. It cannot work, and understanding why is worth a moment.
JSON is recursive: an object contains values, which may be objects, containing more values, to unlimited depth. Validating it requires counting how many brackets are open at any point — and matching arbitrarily nested brackets is provably beyond what regular expressions can express. Any regex that appears to work is either accepting invalid documents, rejecting valid ones, or both.
// Sometimes suggested. Both of these are wrong.
/^[\[{].*[\]}]$/.test('{"a": 1}'); // true — but so is "{oops]"
/^[\[{].*[\]}]$/.test('"just a string"'); // false — yet this IS valid JSONThat second case catches people out regularly: a bare string, number, true, false or null is a complete, valid JSON document. Validity is not the same as "starts with a brace".
A Better Helper: Return the Value
The boolean version above has a flaw — it throws away the parsed result, so callers parse twice. In practice you almost always want the value and the outcome:
function parseJson(text) {
try {
return { ok: true, value: JSON.parse(text) };
} catch (err) {
return { ok: false, error: err.message };
}
}
const result = parseJson(input);
if (!result.ok) {
showError(result.error);
return;
}
render(result.value);Two details worth keeping. Catch only what you mean to: a bare catch around a large block will swallow unrelated errors from your own code and report them as invalid JSON. And never return a bare null on failure — null is itself a valid parse result, so the caller cannot tell the two cases apart.
Reading the Error Position
"Invalid JSON" is a poor message to show a user. The thrown SyntaxError carries position information you can turn into something actionable:
function describeJsonError(text) {
try {
JSON.parse(text);
return null;
} catch (err) {
// V8: "... in JSON at position 42"
const match = /at position (\d+)/.exec(err.message);
if (!match) return err.message;
const pos = Number(match[1]);
const before = text.slice(0, pos);
const line = before.split("\n").length;
const column = pos - before.lastIndexOf("\n");
return `${err.message} (line ${line}, column ${column})`;
}
}Error text differs across engines — V8 reports a character position, Firefox reports line and column directly — so handle both if you support multiple browsers. Newer V8 versions also include a snippet of the offending text, which is a substantial improvement.
Remember that the reported position is where the grammar first became impossible, which is usually one token after the actual mistake. A missing comma is flagged on the following key. The validator guide explains this in detail.
The fetch Trap
This is the most common JSON error in front-end development, and it is not a JSON problem at all:
// Fragile — throws "Unexpected token < in JSON at position 0"
// whenever the server returns an error page
const data = await fetch("/api/users").then(r => r.json());fetch does not reject on HTTP error statuses. A 404 or 500 resolves normally, and .json() then tries to parse an HTML error page. The fix is to check the response before parsing it:
async function fetchJson(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}`);
}
const contentType = response.headers.get("content-type") ?? "";
if (!contentType.includes("application/json")) {
const preview = (await response.text()).slice(0, 100);
throw new Error(`Expected JSON, got ${contentType}: ${preview}`);
}
return response.json();
}The difference in debugging time is substantial. "Unexpected token <" tells you nothing; "HTTP 502 Bad Gateway" tells you exactly where to look.
Syntax Validity Is Not Enough
Everything above answers "can this be parsed". It says nothing about whether the data is usable. This parses without complaint and will break your application immediately:
{
"userId": "not-a-number",
"email": null,
"roles": "admin"
}For a small number of known fields, check by hand:
function isValidUser(value) {
return (
typeof value === "object" &&
value !== null &&
!Array.isArray(value) &&
typeof value.userId === "number" &&
typeof value.email === "string" &&
Array.isArray(value.roles)
);
}The value !== null check is not optional — typeof null is "object", a long-standing JavaScript quirk that silently defeats the naive version of this test.
Beyond a handful of fields, hand-written checks become unmaintainable. Use a schema library instead: Zod if you want TypeScript types inferred from the schema, Ajv if you need standards-compliant JSON Schema that a backend in another language can share.
import { z } from "zod";
const User = z.object({
userId: z.number().int().positive(),
email: z.string().email(),
roles: z.array(z.string()),
});
const result = User.safeParse(JSON.parse(raw));
if (!result.success) {
console.error(result.error.issues); // field-level detail
}Validating in the Browser Console
For a quick one-off check without writing a helper:
// Throws with a position if invalid, otherwise prints the value
JSON.parse(document.querySelector("#payload").textContent);
// Round-trip to inspect a formatted version
console.log(JSON.stringify(JSON.parse(raw), null, 2));Or paste the document into the parser on this site, which reports the error line and column directly and runs entirely in your browser — nothing is uploaded, so production payloads are safe to check.
Frequently Asked Questions
How do I check if a string is valid JSON in JavaScript?
Call JSON.parse inside a try/catch. There is no built-in isValidJson or JSON.isValid, and no regular expression can do the job correctly, because JSON's grammar is recursive and regular expressions cannot match arbitrary nesting. A successful parse is the definition of validity.
Is JSON.parse slow enough that I should avoid it for validation?
No. JSON.parse is implemented in native code and is extremely fast — typically faster than any JavaScript-level check you could write. Parsing and discarding the result is the correct approach for validation on anything short of megabyte-scale documents in a hot loop.
Why does my fetch response fail with 'Unexpected token < in JSON'?
You are parsing HTML. The server returned an error page, a redirect to a login screen, or a 404 — and response.json() tried to parse '<!DOCTYPE html>'. Check response.ok and the Content-Type header before calling .json().
Does JSON.parse validate my data structure?
No. It only confirms the text is well-formed JSON. A document can parse perfectly and still be missing required fields or carry wrong types. Structural checking is a separate step, done by hand or with a schema validator such as Ajv or Zod.
Is JSON.parse safe on untrusted input?
It cannot execute code, unlike eval(), which was used for this before JSON.parse existed. The remaining concerns are resource exhaustion on very large payloads and prototype pollution if you deep-merge parsed data into existing objects without guarding against __proto__ keys.