DevTools Hub

Search tools

Search for a developer tool

How to Validate JSON in JavaScript

Part of the JSON Toolkit

"Is this valid JSON?" actually asks two different questions, and mixing them up is where most validation bugs come from. One is syntax: does the string parse as JSON at all? The other is shape: does the parsed value have the fields, types, and structure your code actually needs? JSON.parse only answers the first one.

Syntax validation: JSON.parse in a try/catch

JSON.parse throws a SyntaxError on malformed input, so wrapping it in a try/catch is the entire mechanism for checking whether a string is parseable JSON:

function isValidJson(str) {
  try {
    JSON.parse(str);
    return true;
  } catch {
    return false;
  }
}

isValidJson('{"a":1}');   // true
isValidJson('{"a":1,}');  // false — trailing comma
isValidJson("{bad json"); // false

The error message tells you where parsing gave up — V8 (Chrome, Node, Edge) reports it as Expected property name or '}' in JSON at position 1 for the trailing-comma case above. For the full list of syntax mistakes that trigger this and how to read the message, see Common JSON Errors and How to Fix Them.

A common misconception: JSON isn't just objects and arrays

It's easy to assume valid JSON always starts with { or [, but the JSON spec allows any value at the top level — a bare number, a quoted string, true, false, or null are all complete, valid JSON documents on their own:

isValidJson("42");        // true
isValidJson('"hello"');   // true — note the string is itself quoted
isValidJson("null");      // true
isValidJson("hello");     // false — unquoted, not valid JSON
isValidJson("undefined"); // false — not JSON; that's a JS-only value
isValidJson("NaN");       // false — same reason

That last pair matters if you're validating data that might have come from JSON.stringify'd JavaScript rather than a spec-compliant source: undefined and NaN have no JSON representation. JSON.stringify silently drops undefined object properties and turns NaN/Infinity into null — so round-tripping through JSON can lose information even when nothing throws.

Syntax-valid isn't shape-valid

This passes isValidJson without any error:

isValidJson('{"username": 42, "email": null}'); // true

It's perfectly well-formed JSON — and almost certainly not what a function expecting { username: string, email: string } wants. A try/catch around JSON.parse catches typos and malformed strings; it says nothing about whether the right fields are present, or the right type.

Hand-rolled shape validation

For a handful of required fields, a manual check after parsing is often enough:

function parseUser(str) {
  const data = JSON.parse(str); // throws on bad syntax

  if (typeof data !== "object" || data === null || Array.isArray(data)) {
    throw new Error("expected a JSON object");
  }
  if (typeof data.username !== "string" || data.username.length === 0) {
    throw new Error("username must be a non-empty string");
  }
  if (typeof data.email !== "string") {
    throw new Error("email must be a string");
  }

  return data;
}

Note the typeof data !== "object" check alone isn't enough — typeof null === "object" in JavaScript, and Array.isArray also passes a plain typeof object check, so both need an explicit guard if the shape you want is specifically a non-null, non-array object.

This approach scales poorly past a few fields: nested objects, optional properties, arrays of a specific shape, and cross-field rules turn into a wall of if statements that's tedious to write and easy to get subtly wrong.

JSON Schema for anything bigger

Past that point, describing the shape declaratively as a JSON Schema and validating against it with a library — ajv is the standard choice in JavaScript — reads better and catches more:

import Ajv from "ajv";

const ajv = new Ajv();
const schema = {
  type: "object",
  properties: {
    username: { type: "string", minLength: 1 },
    age: { type: "number", minimum: 0 },
  },
  required: ["username", "age"],
  additionalProperties: false,
};

const validate = ajv.compile(schema);
const valid = validate({ username: "ada", age: -1 });

console.log(valid); // false
console.log(validate.errors);
// [{ instancePath: "/age", keyword: "minimum", message: "must be >= 0", ... }]

validate.errors lists every failing rule with its exact path in the document — not just the first one, which matters when you want to show a user everything wrong with their input at once rather than one error per resubmission. For how that keyword-checking actually works under the hood — why it doesn't stop at the first failure, and how $ref/$defs let you reuse schemas — see JSON Schema Validation Explained. Don't have a schema yet? JSON Schema Generator writes a first draft from a sample document.

Common mistakes specific to this case

  • Treating a successful JSON.parse as full validation. It proves the string is syntactically valid JSON, nothing about whether the resulting value has the shape the rest of the code assumes.
  • Checking typeof value === "object" without excluding null and arrays. Both pass that check; add an explicit value !== null and !Array.isArray(value) guard if the shape you want excludes them.
  • Parsing a value that's already an object. If the JSON already went through JSON.parse once — a framework that auto-parses request bodies, for instance — calling JSON.parse again throws, since it now receives an object, not a string.
  • Skipping validation on trusted-looking input. A request body, a localStorage value, a config file someone hand-edited — all are just strings from JSON's perspective, and none are guaranteed to match the shape your code expects just because they usually do.

Try it yourself

For a syntax check with the exact line and column of the first error, use JSON Validator. To check that a document also has the right shape, paste it and a schema into JSON Schema Validator — it reports every failing rule and where it failed, the same information ajv gives you in code. Both run entirely in your browser; nothing you paste is uploaded.

Related tools