100% Private

JSON Validation: A Complete Developer's Guide

JSON is simple enough to read by eye, but its syntax rules are strict enough that a single misplaced comma can break an entire pipeline. This guide covers JSON syntax rules, how to validate JSON in different languages, JSON Schema for structural validation, JSONPath for querying, pretty-printing, minifying, diffing, and every common error you'll encounter and how to fix it.

JSON Syntax Rules

JSON (JavaScript Object Notation) is defined by RFC 8259. The specification is intentionally small. Valid JSON has exactly six data types:

TypeExampleNotes
String"hello world"Double quotes only. Never single quotes.
Number42, 3.14, -17, 1.5e10No quotes. No leading zeros (except "0.x"). No NaN or Infinity.
Booleantrue, falseLowercase only. True and False are invalid.
NullnullLowercase only.
Array[1, "two", true, null]Ordered. Can mix types. No trailing comma.
Object{"key": "value"}Keys must be double-quoted strings. No trailing comma.

The Rules That Trip People Up

Double quotes are mandatory. This is different from JavaScript where both 'text' and "text" work. In JSON, 'text' is a syntax error.

No trailing commas. The last element in an array and the last property in an object cannot have a trailing comma. JavaScript added trailing comma support in ES5, but JSON never did.

No comments. RFC 8259 explicitly excludes comments. They were removed from an early draft. If you need comments in config files, use JSON5 (superset with comments), JSONC (JSON with Comments, used by VS Code), or YAML.

No undefined. JavaScript has undefined; JSON doesn't. Use null for absent values.

No JavaScript expressions. No functions, no new Date(), no Infinity, no NaN. JSON is a data format, not JavaScript.

Object keys must be strings. In JavaScript, {1: "one"} is valid (key gets coerced to string "1"). In JSON, keys must be quoted strings explicitly: {"1": "one"}.

Valid JSON Example

{
"name": "Jane Smith",
"age": 29,
"active": true,
"email": null,
"scores": [98, 87, 92],
"address": {
"city": "Portland",
"state": "OR",
"zip": "97201"
}
}

String Escape Sequences

Inside JSON strings, these are the only valid escape sequences:

"    Double quote (needed to embed " inside a string)
\    Backslash
/    Forward slash (optional, for compatibility)
\b    Backspace
\f    Form feed
\n    Newline
\r    Carriage return
\t    Tab
\uXXXX  Unicode character (4 hex digits)

Literal newlines inside strings are not allowed — they must be escaped as \n. This is a frequent source of confusion for people coming from YAML or TOML.

Common Errors and Fixes

Single Quotes Instead of Double Quotes

// Invalid
{'name': 'John', 'active': true}
// Valid
{"name": "John", "active": true}

This is the most common error from people who write a lot of JavaScript. Train your fingers to use double quotes for JSON.

Trailing Commas

// Invalid — comma after last item
{
"name": "John",
"age": 30,
}

// Also invalid in arrays [1, 2, 3,]

// Valid
{
"name": "John",
"age": 30
}

Text editors with JSON awareness (VS Code, WebStorm) will flag trailing commas. If you frequently write JSON by hand, enable JSON linting in your editor.

Unquoted Keys

// Invalid
{name: "John", age: 30}
// Valid
{"name": "John", "age": 30}

JavaScript-Specific Values

// Invalid — JavaScript constructs not allowed in JSON
{
"fn": function() { return 42; },
"value": undefined,
"timestamp": new Date(),
"infinity": Infinity,
"nan": NaN
}
// Valid — use JSON-compatible representations
{
"fn": null,
"value": null,
"timestamp": "2024-03-15T10:30:00Z",
"infinity": null,
"nan": null
}

Unescaped Special Characters

// Invalid — unescaped double quote inside string
{"message": "She said "Hello""}
// Valid
{"message": "She said "Hello""}

Literal Newlines in Strings

// Invalid — literal newline inside string value
{
"poem": "Roses are red
Violets are blue"
}
// Valid
{"poem": "Roses are red\nViolets are blue"}

Comments

// Invalid — JSON doesn't support comments
{
// Database configuration
"host": "localhost",
"port": 5432  /* PostgreSQL default */
}
// Valid — use _comment convention as workaround
{
"_comment": "Database configuration",
"host": "localhost",
"port": 5432
}

Or switch to JSON5 / JSONC for config files where comments are important.

Number Edge Cases

// Invalid numbers
{"a": 01}       // Leading zero (except 0.x is fine)
{"b": .5}       // Missing leading digit (must be 0.5)
{"c": 1.}       // Trailing dot
{"d": Infinity} // Not a valid JSON number
{"e": NaN}      // Not a valid JSON number
// Valid
{"a": 1, "b": 0.5, "c": 1.0, "d": null, "e": null}

Validating in JavaScript, Python, and CLI

JavaScript / Node.js

JSON.parse() throws a SyntaxError on invalid JSON:

function validateJSON(str) {
try {
const parsed = JSON.parse(str);
return { valid: true, data: parsed };
} catch (e) {
return {
valid: false,
error: e.message
// e.message includes position info: "Unexpected token , in JSON at position 42"
};
}
}

// Usage const result = validateJSON('{"name": "John", "age": 30}'); console.log(result.valid); // true

const bad = validateJSON('{"name": "John", "age": 30,}'); console.log(bad.valid); // false console.log(bad.error); // "Unexpected token } in JSON at position 27"

// Validating a JSON file in Node.js import fs from 'fs'; try { const data = JSON.parse(fs.readFileSync('data.json', 'utf-8')); console.log('Valid JSON'); } catch (e) { console.error('Invalid JSON:', e.message); process.exit(1); }

Python

import json

def validate_json(json_string: str) -> tuple[bool, str]: try: json.loads(json_string) return True, "" except json.JSONDecodeError as e: return False, f"Line {e.lineno}, col {e.colno}: {e.msg}"

Usage

valid, err = validate_json('{"name": "John"}') print(valid) # True

valid, err = validate_json("{'name': 'John'}") print(valid, err) # False, "Line 1, col 2: Expecting property name enclosed in double quotes"

Validating a file

import sys try: with open('data.json') as f: json.load(f) print("Valid JSON") except json.JSONDecodeError as e: print(f"Invalid: {e}", file=sys.stderr) sys.exit(1)

CLI with jq

jq is the Swiss Army knife for JSON on the command line:

# Validate (exit code 0 = valid, non-zero = invalid)
jq . data.json > /dev/null && echo "Valid" || echo "Invalid"

Or with explicit exit code check

if jq -e . data.json > /dev/null 2>&1; then echo "Valid JSON" else echo "Invalid JSON" fi

Pretty-print while validating

jq . data.json

Compact output

jq -c . data.json

Validate and show error details

jq . invalid.json 2>&1 # jq prints errors to stderr with position info

Validate multiple files

for f in *.json; do if jq -e . "$f" > /dev/null 2>&1; then echo "OK: $f" else echo "FAIL: $f" fi done

The JSON Validator and JSON Formatter on ToolsDock handle this without any command-line setup — paste or upload, get results instantly.

JSON Schema

JSON syntax validation tells you whether a document is parseable. JSON Schema validation tells you whether it has the right structure and data types. They're complementary — a JSON document can be syntactically perfect but structurally wrong for your application.

What JSON Schema Validates

  • Data types (type: "string", "number", "boolean", "array", "object", "null")
  • Required properties and allowed additional properties
  • String patterns (pattern), min/max length
  • Number ranges (minimum, maximum, multipleOf)
  • Array length (minItems, maxItems) and item types
  • Allowed values (enum)
  • Complex conditions (if/then/else, oneOf, anyOf, allOf)

A Real JSON Schema Example

Suppose you have an API that accepts user profile updates. The schema defines exactly what's valid:

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "User Profile Update",
"type": "object",
"required": ["id", "email"],
"additionalProperties": false,
"properties": {
"id": {
"type": "integer",
"minimum": 1,
"description": "User ID — must be a positive integer"
},
"email": {
"type": "string",
"format": "email",
"maxLength": 255
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"age": {
"type": "integer",
"minimum": 18,
"maximum": 120
},
"role": {
"type": "string",
"enum": ["admin", "editor", "viewer"]
},
"tags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 50
},
"uniqueItems": true,
"maxItems": 10
},
"metadata": {
"type": "object",
"description": "Arbitrary key-value metadata"
}
}
}

This schema enforces: id and email are required; age must be 18-120 if present; role must be one of the three specified values; tags must be unique strings. No extra properties are allowed.

Validating Against a Schema in JavaScript

The two most popular JSON Schema validators for JavaScript are Ajv (fastest) and others:

import Ajv from 'ajv';
import addFormats from 'ajv-formats'; // for "email", "date-time", etc.

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

const schema = { type: 'object', required: ['id', 'email'], properties: { id: { type: 'integer', minimum: 1 }, email: { type: 'string', format: 'email' }, role: { type: 'string', enum: ['admin', 'editor', 'viewer'] } }, additionalProperties: false };

const validate = ajv.compile(schema);

const data = { id: 42, email: 'user@example.com', role: 'admin' }; const valid = validate(data);

if (!valid) { console.log(validate.errors); // [{ instancePath: '/email', message: 'must match format "email"', ... }] } else { console.log('Valid!'); }

Validating Against a Schema in Python

<code">from jsonschema import validate, ValidationError, SchemaError
import json

schema = { "type": "object", "required": ["id", "email"], "properties": { "id": {"type": "integer", "minimum": 1}, "email": {"type": "string", "format": "email"}, "role": {"type": "string", "enum": ["admin", "editor", "viewer"]} }, "additionalProperties": False }

data = {"id": 42, "email": "user@example.com", "role": "admin"}

try: validate(instance=data, schema=schema) print("Valid") except ValidationError as e: print(f"Invalid: {e.message}") print(f"Path: {' -> '.join(str(p) for p in e.path)}") except SchemaError as e: print(f"Schema itself is invalid: {e.message}")

Schema Composition

JSON Schema lets you compose schemas to avoid repetition and express complex rules:

<code">{
"oneOf": [
{ "required": ["creditCard"] },
{ "required": ["bankAccount"] }
],
"properties": {
"creditCard": {
"type": "object",
"required": ["number", "expiry", "cvv"],
"properties": {
"number": { "type": "string", "pattern": "^\d{16}$" },
"expiry": { "type": "string", "pattern": "^\d{2}/\d{2}$" },
"cvv": { "type": "string", "pattern": "^\d{3,4}$" }
}
},
"bankAccount": {
"type": "object",
"required": ["routing", "account"],
"properties": {
"routing": { "type": "string", "pattern": "^\d{9}$" },
"account": { "type": "string" }
}
}
}
}

oneOf requires exactly one of the sub-schemas to match. anyOf allows one or more. allOf requires all. if/then/else enables conditional validation — validate differently based on a field's value.

JSON Path Queries

JSONPath is to JSON what XPath is to XML — a query language for extracting values from JSON documents. It's useful for validation (does this path exist?), transformation (extract specific fields), and testing (assert expected values).

JSONPath Syntax

Given this JSON:
{
"store": {
"books": [
{ "title": "Clean Code", "price": 29.99, "inStock": true },
{ "title": "The Pragmatic Programmer", "price": 39.99, "inStock": false },
{ "title": "SICP", "price": 0.00, "inStock": true }
],
"currency": "USD"
}
}

$.store.currency → "USD" $.store.books[0].title → "Clean Code" $.store.books[*].title → ["Clean Code", "The Pragmatic Programmer", "SICP"] $.store.books[?(@.price < 30)] → books where price < 30 $.store.books[?(@.inStock)] → books where inStock is true $..title → All titles (recursive descent) $.store.books[-1:] → Last book in array

JSONPath in JavaScript

<code">// Using jsonpath-plus library
import { JSONPath } from 'jsonpath-plus';

const data = { store: { books: [...] } };

// Get all in-stock book titles const titles = JSONPath({ path: '$.store.books[?(@.inStock)].title', json: data }); // ["Clean Code", "SICP"]

// Check existence const hasBooks = JSONPath({ path: '$.store.books', json: data }).length > 0;

// Using native JSON + array methods (often simpler for simple queries) const inStockBooks = data.store.books.filter(b => b.inStock); const cheapBooks = data.store.books.filter(b => b.price < 30);

jq for JSONPath-style Queries

jq has its own filter syntax that's more powerful than standard JSONPath:

<code"># Get all book titles
jq '.store.books[].title' data.json

Filter in-stock books and get their titles

jq '[.store.books[] | select(.inStock) | .title]' data.json

Get books under $30

jq '[.store.books[] | select(.price < 30)]' data.json

Transform: create a summary object

jq '{total: (.store.books | length), inStock: [.store.books[] | select(.inStock)] | length}' data.json

Extract values and format as CSV

jq -r '.store.books[] | [.title, .price] | @csv' data.json

Pretty-Printing and Minifying

Why Format Matters

Minified JSON — all on one line — is fine for machines but hard for humans to debug. Pretty-printed JSON with consistent indentation makes structure immediately visible. The tradeoff: whitespace adds bytes (usually 10-30% overhead). For API responses over the wire, minify. For log files and config files humans read, pretty-print.

Pretty-Printing

# JavaScript — built in
JSON.stringify(data, null, 2);  // 2-space indent
JSON.stringify(data, null, 4);  // 4-space indent
JSON.stringify(data, null, '\t'); // Tab indent

Python — built in

import json json.dumps(data, indent=2) json.dumps(data, indent=2, sort_keys=True) # Also sort keys alphabetically

jq

jq . data.json # Default pretty-print (2 spaces) jq --tab . data.json # Tab indent

Node.js one-liner

node -e "console.log(JSON.stringify(require('./data.json'), null, 2))"

Minifying

<code"># JavaScript
JSON.stringify(data);  // No whitespace by default

Python

json.dumps(data, separators=(',', ':')) # Remove all whitespace

jq

jq -c . data.json # Compact (minified) output

Node.js one-liner

node -e "process.stdout.write(JSON.stringify(require('./data.json')))"

The JSON Formatter and JSON Validator handle both operations without any setup.

Sorting Keys

JSON objects are technically unordered (the spec says so), but sorting keys alphabetically makes diffs cleaner and makes it easier to check for the presence of a field:

<code"># Python
json.dumps(data, indent=2, sort_keys=True)

jq

jq -S . data.json # -S sorts keys

JavaScript — no built-in key sort; use a replacer

function sortedStringify(obj) { return JSON.stringify(obj, Object.keys(obj).sort(), 2); } // Note: this only sorts top-level keys; for deep sorting, recurse

Diffing JSON

Comparing two JSON documents with a text diff often produces noisy results — a key order change creates a huge diff even if the actual data is identical. A semantic JSON diff understands that {"a":1,"b":2} and {"b":2,"a":1} are equivalent.

CLI Diff with jq

<code"># Sort both files before diff to normalize key order
diff <(jq -S . file1.json) <(jq -S . file2.json)

Or use json-diff (npm package)

npx json-diff file1.json file2.json

Show only changed values

npx json-diff --json file1.json file2.json | jq .

Diff in JavaScript

<code"># Using deep-diff library
import { diff } from 'deep-diff';

const obj1 = { name: "Alice", age: 30, role: "admin" }; const obj2 = { name: "Alice", age: 31, role: "editor" };

const differences = diff(obj1, obj2); // [ // { kind: 'E', path: ['age'], lhs: 30, rhs: 31 }, // { kind: 'E', path: ['role'], lhs: 'admin', rhs: 'editor' } // ]

// kind: 'E' = edited, 'N' = new, 'D' = deleted, 'A' = array change

The JSON Diff tool at ToolsDock shows a visual side-by-side diff with added, removed, and changed values highlighted.

Linting vs Validation

These terms get mixed up. They're different:

JSON Syntax ValidationJSON Schema ValidationJSON Linting
ChecksIs it parseable?Does it match the schema?Style and conventions
CatchesMissing commas, wrong quotesWrong types, missing fieldsInconsistent formatting, key order
ToolsJSON.parse, jq, json-validatorAjv, jsonschemajsonlint, ESLint
When to useAlways — first stepFor APIs and structured dataIn development, CI

A typical pipeline: syntax validate first, schema validate second, lint for style in development. All three serve different purposes.

ESLint for JSON

<code"># Install ESLint JSON plugin
npm install --save-dev eslint-plugin-json

.eslintrc.json

{ "plugins": ["json"], "rules": { "json/*": ["error"] } }

Lint JSON files

npx eslint --ext .json .

Best Practices

API Design

  • Always set Content-Type: application/json; charset=utf-8
  • Use ISO 8601 for dates: "2024-03-15T10:30:00Z" — not timestamps, not new Date()
  • Be consistent with naming: pick camelCase or snake_case and stick to it across all endpoints
  • Use null for absent values — don't omit fields sometimes and include them as null other times
  • Don't use numbers as IDs if they might exceed 2^53 (JavaScript's safe integer limit). Use strings for large IDs.
  • Version your schema — add a version field when the format might change

Configuration Files

  • Use JSONC (with comments) for human-authored config files when your tool supports it
  • Consider YAML or TOML for config files that humans write frequently — they're more writable and allow comments
  • Validate configuration against a JSON Schema on application startup — fail fast with clear error messages rather than cryptic runtime errors
  • Use environment-specific files (config.dev.json, config.prod.json) with a base that's merged

Working with Large JSON Files

  • Streaming parsers (like stream-json in Node.js) avoid loading the entire file into memory
  • NDJSON (Newline-Delimited JSON) — one JSON object per line — is much more efficient for large datasets and logs
  • For files over a few MB, consider binary formats like MessagePack or CBOR for storage and transmission

Tools

JSON Validator

Check JSON syntax with detailed error messages pointing to the exact problem location.

Validate JSON
JSON Formatter

Beautify minified JSON with configurable indentation. Also minifies formatted JSON.

Format JSON
JSON Schema Validator

Validate JSON against a JSON Schema — check structure, types, and required fields.

Schema Validate
JSON Diff

Compare two JSON documents side-by-side with semantic diffing.

Diff JSON

Quick Reference

Valid JSON
  • Double quotes: "text"
  • No trailing commas
  • Lowercase booleans: true / false
  • Lowercase null: null
  • Quoted keys: {"key": "value"}
  • Escaped special chars: " \ \n
Invalid JSON
  • Single quotes: 'text'
  • Trailing commas: [1, 2, 3,]
  • Comments: // or /* */
  • Undefined: undefined
  • Unquoted keys: {key: "value"}
  • JavaScript functions: function() {}

Privacy Notice: This site works entirely in your browser. We don't collect or store your data. Optional analytics help us improve the site. You can deny without affecting functionality.