Just Formatter
← Back to blog

json

JSON Schema Validation vs JSON Lint — When to Use What

2026-05-279 min read
Try it now — JSON Formatter & ValidatorOpen full screen →

Two different problems, two different tools

JSON Lint and JSON Schema validation are often conflated because both 'validate JSON', but they answer completely different questions. JSON Lint answers: is this string valid JSON syntax? JSON Schema answers: does this JSON object have the right structure, types, and values for my application?

A document can pass JSON Lint (valid syntax) and still fail schema validation (wrong structure). And a schema validator always runs a syntax check implicitly — you cannot validate structure without first parsing the JSON. The two tools operate at different layers, and the right choice depends entirely on what you are trying to catch.

What JSON Lint does

JSON Lint is a syntax checker. It parses your JSON string according to the JSON specification (RFC 8259) and reports the first character where the syntax becomes invalid. Trailing commas, single-quoted strings, comments, missing closing brackets — these are the errors JSON Lint catches.

// JSON Lint — syntax checking only
function lintJson(input) {
  try {
    JSON.parse(input);
    return { valid: true };
  } catch (err) {
    // err.message includes position info like "at position 42"
    return { valid: false, error: err.message };
  }
}

// These fail JSON Lint:
lintJson('{ "name": "Alice", }');          // trailing comma
lintJson("{ 'name': 'Alice' }");            // single quotes
lintJson('{ "count": 1, // total }');       // comment
lintJson('{ "x": undefined }');             // undefined is not JSON

// This PASSES JSON Lint but may fail schema validation:
lintJson('{ "age": "not-a-number" }');      // valid syntax, wrong type

The built-in JSON.parse is a perfectly adequate JSON linter. Third-party linters like jsonlint add better error messages with line and column numbers, which is why formatters use them — JSON.parse only tells you 'position 42', while a linter says 'line 3, column 15: expected property name'.

When to use JSON Lint

Use a JSON linter when you are dealing with hand-authored JSON. Config files, JSON fixtures in test suites, and API responses pasted from curl or Postman are the prime candidates. Human-authored JSON frequently has trailing commas, comments, or unquoted keys — none of which are valid JSON.

  • Validating hand-authored config files (package.json, tsconfig.json, .eslintrc)
  • Checking JSON fixtures in test suites written by hand
  • Debugging API responses that fail to parse unexpectedly
  • CI pipeline checks on JSON data files committed to source control
  • Any situation where you need to know 'is this valid JSON at all?' before trying to use it

What JSON Schema does

JSON Schema is a vocabulary for describing the structure of JSON data. A schema document specifies what properties an object must have, what types each property must be, minimum and maximum values for numbers, regex patterns for strings, and whether additional properties are allowed. A validator checks a JSON document against a schema and reports all violations.

userSchema.js
// JSON Schema example — describes a User object
const userSchema = {
  type: 'object',
  required: ['id', 'name', 'email'],
  properties: {
    id: { type: 'integer', minimum: 1 },
    name: { type: 'string', minLength: 1, maxLength: 100 },
    email: { type: 'string', format: 'email' },
    age: { type: 'integer', minimum: 0, maximum: 150 },
    role: { type: 'string', enum: ['admin', 'user', 'guest'] },
    tags: { type: 'array', items: { type: 'string' } },
  },
  additionalProperties: false,
};

// This passes: all required fields present, correct types
const valid = { id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin' };

// This fails: 'id' is a string (should be integer), 'name' is missing
const invalid = { id: 'abc', email: 'alice@example.com' };

JSON Schema is the right tool when you need to enforce contracts between systems — an API that receives JSON should validate the incoming payload against a schema before processing it, catching missing fields, wrong types, and out-of-range values before they cause data corruption or runtime errors.

Schema validation with AJV

AJV (Another JSON Validator) is the most widely used JSON Schema validator for JavaScript. It compiles schemas to optimized validation functions and supports JSON Schema drafts 04, 06, 07, and 2019-09, plus formats like email and uri via the ajv-formats plugin.

ajvValidation.js
import Ajv from 'ajv';
import addFormats from 'ajv-formats';

const ajv = new Ajv({ allErrors: true }); // report all errors, not just the first
addFormats(ajv);

const validate = ajv.compile(userSchema);

function validateUser(data) {
  const valid = validate(data);
  if (!valid) {
    const errors = validate.errors.map(e => ({
      field: e.instancePath || e.schemaPath,
      message: e.message,
      value: e.data,
    }));
    return { valid: false, errors };
  }
  return { valid: true };
}

// Example output for invalid input:
// { valid: false, errors: [
//   { field: '/id', message: 'must be integer', value: 'abc' },
//   { field: '', message: "must have required property 'name'" }
// ]}

Compile the validate function once at startup — AJV compilation is expensive but execution is fast. Recompiling on every request adds unnecessary overhead.

Combining both in a production workflow

In practice, you use both tools at different stages. JSON Lint runs at the parsing boundary — when you receive raw JSON input from a file, a user, or an external API — to catch syntax errors before they produce confusing runtime failures. JSON Schema validation runs after parsing, on the structured JavaScript object, to enforce your application's data contract.

async function processApiPayload(rawJson) {
  // Stage 1: syntax check (linting)
  let parsed;
  try {
    parsed = JSON.parse(rawJson);
  } catch (err) {
    throw new Error(`Invalid JSON syntax: ${err.message}`);
  }

  // Stage 2: schema validation
  const result = validateUser(parsed);
  if (!result.valid) {
    throw new Error(
      'Payload does not match expected schema: ' +
      result.errors.map(e => e.message).join(', ')
    );
  }

  // Stage 3: business logic — safe to use parsed data
  return createUser(parsed);
}

This two-stage pattern is the standard approach in production APIs. Stage 1 catches malformed input from external systems. Stage 2 enforces your application's contract. Neither stage makes the other redundant.

Quick decision guide

Choose based on what question you need to answer:

  • Is this string valid JSON? → JSON Lint (Just Formatter, JSON.parse, jsonlint)
  • Does this object have the right structure for my API? → JSON Schema (AJV, Zod, Yup)
  • I'm writing a config file by hand → JSON Lint to catch typos before committing
  • I'm building an API that receives user data → JSON Schema to enforce the contract
  • I'm debugging a response that fails to parse → JSON Lint with exact line numbers
  • I'm generating TypeScript types from a contract → JSON Schema + json-schema-to-typescript