# JSON Schema Validation: Complete Guide with Examples

return (
    
      /* Schema.org JSON-LD */
      
      
      

      /* Breadcrumb Navigation */
      
        [Home](/)
        /
        [Blog](/blog/)
        /
        JSON Schema Validation Guide
      

      /* Article Header */
      
        # 
          JSON Schema Validation: Complete Guide with Examples
        
        
          JSON
          March 7, 2026
          15 min read
        
      

      /* Article Content */
      

        
          ## What Is JSON Schema?
          
            JSON Schema is a declarative language for describing the structure and constraints of JSON data. Think of it as TypeScript types for your JSON -- it defines what fields exist, what types they have, which ones are required, and what values are valid. But unlike TypeScript, JSON Schema validation happens at runtime, which makes it perfect for validating API payloads, configuration files, form submissions, and any data that crosses trust boundaries.
          

          
            A JSON Schema is itself a JSON document. It describes another JSON document. When you validate data against a schema, the validator checks every constraint and returns a list of violations if the data does not conform. This approach is language-agnostic -- the same schema works in JavaScript, Python, Go, Java, C#, and every other language with a JSON Schema library.
          

          
            `// A simple JSON Schema

  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": 
    "name":  "type": "string", "minLength": 1 ,
    "email":  "type": "string", "format": "email" ,
    "age":  "type": "integer", "minimum": 0, "maximum": 150 
  ,
  "required": ["name", "email"]

// Valid data:
 "name": "Alice", "email": "alice@example.com", "age": 30 

// Invalid data (missing required field, wrong type):
 "email": 123 
// Errors: "name" is required, "email" must be a string`
          
          
            To experiment with JSON structures before writing schemas, format your data with our [JSON Formatter](/json-formatter/) -- it validates syntax, highlights errors, and pretty-prints with configurable indentation.
          

        

        
          ## Type System: The Foundation
          
            Every JSON Schema starts with a type declaration. JSON Schema supports seven primitive types that map directly to JSON value types.
          

          
            
              
                
                  Type
                  JSON Value
                  Example
                
              
              
                stringText in double quotes&quot;hello&quot;
                numberAny numeric value3.14, -1, 2e10
                integerWhole numbers only42, -7, 0
                booleantrue or falsetrue
                objectKey-value pairs`"key": "value"`
                arrayOrdered list[1, 2, 3]
                nullNull valuenull
              
            
          
          
            `// Allow multiple types (nullable string)
 "type": ["string", "null"] 
// Accepts: "hello", null
// Rejects: 42, true

// Enum: restrict to specific values
 "type": "string", "enum": ["active", "inactive", "pending"] 

// Const: restrict to exactly one value
 "const": "production" `
          
        

        
          ## String Constraints
          
            Strings support length constraints, regex pattern matching, and built-in format validators. The format keyword provides semantic validation for common string types like emails, URIs, dates, and UUIDs.
          

          
            `// String with length and pattern constraints

  "type": "string",
  "minLength": 3,
  "maxLength": 50,
  "pattern": "^[a-zA-Z][a-zA-Z0-9_]*$"

// Accepts: "username_123"
// Rejects: "ab" (too short), "123abc" (doesn't start with letter)

// Built-in format validators
 "type": "string", "format": "email"         // RFC 5321 email
 "type": "string", "format": "uri"           // Full URI with scheme
 "type": "string", "format": "date"          // ISO 8601 date (YYYY-MM-DD)
 "type": "string", "format": "date-time"     // ISO 8601 datetime
 "type": "string", "format": "uuid"          // UUID any version
 "type": "string", "format": "ipv4"          // IPv4 address
 "type": "string", "format": "ipv6"          // IPv6 address
 "type": "string", "format": "hostname"      // Internet hostname
 "type": "string", "format": "json-pointer"  // JSON Pointer (RFC 6901)`
          
          
            The pattern keyword accepts any valid regular expression. For building and testing regex patterns, use our [Regex Tester](/regex-tester/) to verify they match your expected inputs before embedding them in schemas.
          

        

        
          ## Number Constraints
          
            `// Integer with range

  "type": "integer",
  "minimum": 1,
  "maximum": 100

// Number with exclusive bounds

  "type": "number",
  "exclusiveMinimum": 0,     // greater than 0 (not equal)
  "exclusiveMaximum": 1      // less than 1 (not equal)

// Accepts: 0.5, 0.001, 0.999
// Rejects: 0, 1, -0.5

// Multiple of (useful for prices, grid sizes)

  "type": "number",
  "multipleOf": 0.01          // two decimal places max

// Accepts: 9.99, 10.00, 0.01
// Rejects: 9.999

// Percentage with step

  "type": "integer",
  "minimum": 0,
  "maximum": 100,
  "multipleOf": 5             // 0, 5, 10, 15, ..., 100
`
          
        

        
          ## Object Schemas: Properties, Required Fields, and More
          
            Objects are the most common type in JSON APIs. JSON Schema gives you fine-grained control over which properties are allowed, which are required, and what additional properties look like.
          

          
            `// Complete API request schema

  "type": "object",
  "properties": 
    "username": 
      "type": "string",
      "minLength": 3,
      "maxLength": 30,
      "pattern": "^[a-zA-Z0-9_]+$"
    ,
    "email": 
      "type": "string",
      "format": "email"
    ,
    "role": 
      "type": "string",
      "enum": ["admin", "editor", "viewer"],
      "default": "viewer"
    ,
    "metadata": 
      "type": "object",
      "additionalProperties":  "type": "string" 
    
  ,
  "required": ["username", "email"],
  "additionalProperties": false

// "additionalProperties": false  → rejects any field not listed in "properties"
// "additionalProperties": true   → allows any extra fields (default behavior)
// "additionalProperties":  "type": "string"  → extra fields must be strings`
          

          ### Property Name Patterns
          
            `// patternProperties: apply schemas based on property name patterns

  "type": "object",
  "patternProperties": 
    "^x-":  "type": "string" ,         // x-* headers must be strings
    "^[0-9]+$":  "type": "boolean"     // numeric keys must be booleans
  ,
  "additionalProperties": false

// Accepts:  "x-custom": "value", "123": true 
// Rejects:  "x-custom": 42  (x-* must be string)`
          

          ### Property Dependencies
          
            `// dependentRequired: if field A exists, field B is required

  "type": "object",
  "properties": 
    "creditCard":  "type": "string" ,
    "billingAddress":  "type": "string" ,
    "couponCode":  "type": "string" 
  ,
  "dependentRequired": 
    "creditCard": ["billingAddress"]   // creditCard requires billingAddress
  

// Accepts:  "couponCode": "SAVE10"  (no card, no address needed)
// Accepts:  "creditCard": "4111...", "billingAddress": "123 St" 
// Rejects:  "creditCard": "4111..."  (missing billingAddress)`
          
        

        
          ## Array Schemas: Items, Tuples, and Uniqueness
          
            `// Array of strings with length constraints

  "type": "array",
  "items":  "type": "string", "minLength": 1 ,
  "minItems": 1,
  "maxItems": 10,
  "uniqueItems": true

// Accepts: ["apple", "banana", "cherry"]
// Rejects: ["apple", "apple"] (not unique)
// Rejects: [] (minItems is 1)

// Tuple validation (Draft 2020-12: prefixItems)

  "type": "array",
  "prefixItems": [
     "type": "string" ,       // first element: string
     "type": "integer" ,      // second element: integer
     "type": "boolean"        // third element: boolean
  ],
  "items": false                // no additional items allowed

// Accepts: ["hello", 42, true]
// Rejects: ["hello", 42, true, "extra"]

// Mixed array (allow strings or numbers)

  "type": "array",
  "items": 
    "oneOf": [
       "type": "string" ,
       "type": "number" 
    ]
  

// Accepts: ["hello", 42, "world", 3.14]
// Rejects: ["hello", true] (boolean not allowed)

// Array of objects (common API pattern)

  "type": "array",
  "items": 
    "type": "object",
    "properties": 
      "id":  "type": "integer" ,
      "name":  "type": "string" 
    ,
    "required": ["id", "name"]
  
`
          
        

        
          ## Composition: allOf, anyOf, oneOf, not
          
            Composition keywords let you combine schemas using logical operators. This is how you model inheritance, polymorphism, and complex validation rules that cannot be expressed with simple property definitions.
          

          
            
              
                
                  Keyword
                  Logic
                  Use Case
                
              
              
                allOfAND -- must match all schemasInheritance, combining constraints
                anyOfOR -- must match at least oneMultiple acceptable formats
                oneOfXOR -- must match exactly oneDiscriminated unions, polymorphism
                notNOT -- must not matchExclusion, blacklisting values
              
            
          
          
            `// allOf: extend a base schema

  "allOf": [
    
      "type": "object",
      "properties": 
        "id":  "type": "integer" ,
        "createdAt":  "type": "string", "format": "date-time" 
      ,
      "required": ["id", "createdAt"]
    ,
    
      "properties": 
        "name":  "type": "string" ,
        "email":  "type": "string", "format": "email" 
      ,
      "required": ["name", "email"]
    
  ]

// Result: object must have id, createdAt, name, AND email

// oneOf: discriminated union (API event types)

  "oneOf": [
    
      "type": "object",
      "properties": 
        "type":  "const": "user.created" ,
        "data": 
          "type": "object",
          "properties":  "userId":  "type": "integer"  ,
          "required": ["userId"]
        
      ,
      "required": ["type", "data"]
    ,
    
      "type": "object",
      "properties": 
        "type":  "const": "order.completed" ,
        "data": 
          "type": "object",
          "properties":  "orderId":  "type": "string"  ,
          "required": ["orderId"]
        
      ,
      "required": ["type", "data"]
    
  ]
`
          
        

        
          ## Conditional Schemas: if / then / else
          
            Conditional schemas let you apply different validation rules based on the data itself. This is extremely useful for forms and API payloads where the required fields depend on a type or mode selection.
          

          
            `// If paymentMethod is "credit_card", require cardNumber and cvv
// If paymentMethod is "bank_transfer", require accountNumber and routingNumber

  "type": "object",
  "properties": 
    "paymentMethod":  "type": "string", "enum": ["credit_card", "bank_transfer"] ,
    "amount":  "type": "number", "minimum": 0.01 
  ,
  "required": ["paymentMethod", "amount"],
  "if": 
    "properties":  "paymentMethod":  "const": "credit_card"  
  ,
  "then": 
    "properties": 
      "cardNumber":  "type": "string", "pattern": "^[0-9]16$" ,
      "cvv":  "type": "string", "pattern": "^[0-9]3,4$" 
    ,
    "required": ["cardNumber", "cvv"]
  ,
  "else": 
    "properties": 
      "accountNumber":  "type": "string" ,
      "routingNumber":  "type": "string", "pattern": "^[0-9]9$" 
    ,
    "required": ["accountNumber", "routingNumber"]
  

// Valid credit card payment:
 "paymentMethod": "credit_card", "amount": 99.99, "cardNumber": "4111111111111111", "cvv": "123" 

// Valid bank transfer:
 "paymentMethod": "bank_transfer", "amount": 500, "accountNumber": "12345678", "routingNumber": "021000021" 

// Invalid (credit card selected but missing cvv):
 "paymentMethod": "credit_card", "amount": 99.99, "cardNumber": "4111111111111111" `
          
        

        
          ## Reusable Schemas with $ref and $defs
          
            Real-world schemas share definitions across endpoints. The $defs keyword (formerly definitions in Draft-07) stores reusable schemas, and $ref references them. This keeps your schemas DRY and maintainable.
          

          
            `
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": 
    "address": 
      "type": "object",
      "properties": 
        "street":  "type": "string" ,
        "city":  "type": "string" ,
        "state":  "type": "string", "pattern": "^[A-Z]2$" ,
        "zip":  "type": "string", "pattern": "^[0-9]5(-[0-9]4)?$" 
      ,
      "required": ["street", "city", "state", "zip"]
    ,
    "phone": 
      "type": "string",
      "pattern": "^\\\\+?[1-9]\\\\d6,14$"
    
  ,
  "type": "object",
  "properties": 
    "name":  "type": "string" ,
    "homeAddress":  "$ref": "#/$defs/address" ,
    "workAddress":  "$ref": "#/$defs/address" ,
    "phone":  "$ref": "#/$defs/phone" 
  ,
  "required": ["name", "homeAddress"]

// Both homeAddress and workAddress validate against the same schema
// Changes to the address definition automatically apply everywhere`
          
          
            For schemas that span multiple files, you can use absolute or relative URIs in $ref. For example, `"$ref": "./address.schema.json"` references a separate file. When working with complex JSON structures, use our [JSON Path Tester](/json-path-tester/) to navigate and extract data from deeply nested documents.
          

        

        
          ## Validation in JavaScript with Ajv
          
            Ajv (Another JSON Schema Validator) is the most popular JSON Schema library for JavaScript and TypeScript. It compiles schemas into optimized validation functions for maximum performance.
          

          
            `// Installation
// npm install ajv ajv-formats

const ajv = new Ajv( allErrors: true );
addFormats(ajv);  // adds "email", "uri", "date-time", etc.

const schema = 
  type: 'object',
  properties: 
    name:  type: 'string', minLength: 1 ,
    email:  type: 'string', format: 'email' ,
    age:  type: 'integer', minimum: 0 ,
  ,
  required: ['name', 'email'],
  additionalProperties: false,
;

const validate = ajv.compile(schema);

// Valid data
const valid = validate( name: 'Alice', email: 'alice@example.com', age: 30 );
console.log(valid); // true

// Invalid data
const invalid = validate( email: 'not-an-email' );
console.log(invalid); // false
console.log(validate.errors);
// [
//    keyword: 'required', params:  missingProperty: 'name'  ,
//    keyword: 'format', params:  format: 'email'  
// ]

// Express.js middleware example
function validateBody(schema) 
  const validate = ajv.compile(schema);
  return (req, res, next) => 
    if (!validate(req.body)) 
      return res.status(400).json(
        error: 'Validation failed',
        details: validate.errors,
      );
    
    next();
  ;

app.post('/api/users', validateBody(schema), (req, res) => 
  // req.body is guaranteed to be valid here
);`
          
        

        
          ## Validation in Python with jsonschema
          
            `# pip install jsonschema[format]

from jsonschema import validate, ValidationError, Draft202012Validator

schema = 
    "type": "object",
    "properties": 
        "name": "type": "string", "minLength": 1,
        "email": "type": "string", "format": "email",
        "tags": 
            "type": "array",
            "items": "type": "string",
            "uniqueItems": True
        
    ,
    "required": ["name", "email"],
    "additionalProperties": False

## Simple validation (raises on first error)
try:
    validate(instance="name": "", "email": "bad", schema=schema)
except ValidationError as e:
    print(f"Error: e.message")
    # Error: '' is too short

## Collect all errors
validator = Draft202012Validator(schema)
errors = list(validator.iter_errors("email": 123))
for error in errors:
    print(f"error.json_path: error.message")
## $.name: 'name' is a required property
## $.email: 123 is not of type 'string'

## FastAPI integration (uses JSON Schema internally)
from pydantic import BaseModel, EmailStr

class UserCreate(BaseModel):
    name: str
    email: EmailStr
    tags: list[str] = []

## Pydantic generates JSON Schema automatically:
print(UserCreate.model_json_schema())`
          
        

        
          ## Validation in Go with gojsonschema
          
            `// go get github.com/xeipuuv/gojsonschema

package main

    "fmt"
    "github.com/xeipuuv/gojsonschema"
)

func main() 
    schema := gojsonschema.NewStringLoader(\`
        "type": "object",
        "properties": 
            "name": "type": "string", "minLength": 1,
            "email": "type": "string", "format": "email"
        ,
        "required": ["name", "email"],
        "additionalProperties": false
    \`)

    document := gojsonschema.NewStringLoader(\`
        "name": "Alice",
        "email": "alice@example.com"
    \`)

    result, err := gojsonschema.Validate(schema, document)
    if err != nil 
        panic(err)
    

    if result.Valid() 
        fmt.Println("Document is valid")
     else 
        for _, err := range result.Errors() 
            fmt.Printf("- %s\\n", err)
        
    
`
          
        

        
          ## Real-World Schema: REST API Response
          
            Here is a complete schema for a paginated API response -- the kind you would use to validate responses from a REST API.
          

          
            `
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": 
    "user": 
      "type": "object",
      "properties": 
        "id":  "type": "integer", "minimum": 1 ,
        "username":  "type": "string", "minLength": 3, "maxLength": 30 ,
        "email":  "type": "string", "format": "email" ,
        "role":  "type": "string", "enum": ["admin", "editor", "viewer"] ,
        "createdAt":  "type": "string", "format": "date-time" ,
        "profile": 
          "type": "object",
          "properties": 
            "displayName":  "type": "string" ,
            "bio":  "type": ["string", "null"], "maxLength": 500 ,
            "avatarUrl":  "type": ["string", "null"], "format": "uri" 
          ,
          "required": ["displayName"]
        
      ,
      "required": ["id", "username", "email", "role", "createdAt"]
    ,
    "pagination": 
      "type": "object",
      "properties": 
        "page":  "type": "integer", "minimum": 1 ,
        "perPage":  "type": "integer", "minimum": 1, "maximum": 100 ,
        "total":  "type": "integer", "minimum": 0 ,
        "totalPages":  "type": "integer", "minimum": 0 
      ,
      "required": ["page", "perPage", "total", "totalPages"]
    
  ,
  "type": "object",
  "properties": 
    "data": 
      "type": "array",
      "items":  "$ref": "#/$defs/user" 
    ,
    "pagination":  "$ref": "#/$defs/pagination" 
  ,
  "required": ["data", "pagination"]
`
          
          
            This schema validates the entire response structure including nested user profiles and pagination metadata. For designing APIs with correct status codes, check our [API Response Codes Best Practices](/blog/api-response-codes-best-practices/) guide and the [HTTP Status Codes](/http-status-codes/) reference.
          

        

        
          ## Schema Generation from TypeScript
          
            If you already have TypeScript interfaces, you can generate JSON Schema automatically instead of writing it by hand. This keeps your types and validation in sync.
          

          
            `// Install: npm install typescript-json-schema -D

// Define your TypeScript interface
interface User 
  /** @minimum 1 */
  id: number;
  /** @minLength 3 @maxLength 30 */
  username: string;
  /** @format email */
  email: string;
  role: 'admin' | 'editor' | 'viewer';
  /** @format date-time */
  createdAt: string;

// Generate schema from CLI:
// npx typescript-json-schema tsconfig.json User --required --strictNullChecks

// Or use Zod (runtime validation + schema generation):

const userSchema = z.object(
  id: z.number().int().min(1),
  username: z.string().min(3).max(30),
  email: z.string().email(),
  role: z.enum(['admin', 'editor', 'viewer']),
  createdAt: z.string().datetime(),
);

const jsonSchema = zodToJsonSchema(userSchema);
console.log(JSON.stringify(jsonSchema, null, 2));`
          
          
            Need to convert JSON data to TypeScript types first? Our [JSON to TypeScript](/json-to-typescript/) converter generates accurate type definitions from any JSON payload. For converting between JSON and other formats, see our [JSON to YAML](/json-to-yaml/) and [JSON to XML](/json-to-xml/) converters.
          

        

        
          ## Common Mistakes and Best Practices
          
            - **Forgetting additionalProperties** -- By default, objects allow any extra fields. Set additionalProperties: false to catch typos and unexpected data in API requests.
            - **Not enabling format validation** -- The format keyword is an annotation by default in most validators. In Ajv, install ajv-formats and call addFormats(ajv). In jsonschema (Python), install with pip install jsonschema[format].
            - **Using oneOf when anyOf works** -- oneOf requires exactly one match. If schemas overlap, validation fails even though the data is valid. Use anyOf unless you specifically need exclusive matching.
            - **Overly strict patterns** -- Regex patterns in schemas should validate structure, not content. A pattern like ^[A-Z][a-z]+$ for names rejects valid names like &quot;O&apos;Brien&quot; or &quot;McDonald&quot;.
            - **Not using $defs for repeated structures** -- Duplicating schemas leads to inconsistencies when you update one copy but forget the others. Extract shared definitions into $defs and reference them with $ref.
            - **Ignoring error messages** -- Default validation errors are technical. Map them to user-friendly messages in your API layer, especially for form validation.
          
        

        
          ## Draft Version Comparison
          
            
              
                
                  Feature
                  Draft-07
                  2019-09
                  2020-12
                
              
              
                if/then/elseYesYesYes
                $defs (was definitions)definitions$defs$defs
                prefixItems (tuples)NoNoYes
                dependentRequiredNoYesYes
                $dynamicRefNoNoYes
                unevaluatedPropertiesNoYesYes
              
            
          
        

        
          ## Frequently Asked Questions

          ### What is JSON Schema and why should I use it?
          
            JSON Schema is a declarative language for describing the structure and constraints of JSON data. It defines allowed types, required fields, string formats, and nested structures. Use it to validate API payloads, configuration files, form data, and any JSON crossing trust boundaries. It prevents invalid data from entering your system and serves as living documentation for data contracts. The same schema works across all programming languages.
          

          ### Which JSON Schema draft version should I use?
          
            Use Draft 2020-12 for new projects. It is the latest stable specification supported by major validators including Ajv, jsonschema, and gojsonschema. It introduced prefixItems for tuples and replaced definitions with $defs. If maintaining existing schemas, Draft-07 remains widely supported and perfectly adequate for most use cases.
          

          ### Can JSON Schema validate nested objects and arrays?
          
            Yes, JSON Schema validates deeply nested structures. Use properties for object fields, items for array elements, and additionalProperties to control extra fields. Nest schemas to any depth, use $ref for shared definitions, and apply conditional validation with if/then/else. Arrays support minItems, maxItems, uniqueItems, and prefixItems for tuple-style validation.
          

        

        /* CTA Section */
        
          ## Format and Validate Your JSON
          
            Paste any JSON into our free formatter to validate syntax, pretty-print with custom indentation, and catch errors before writing your schema.
          

          [
            Open JSON Formatter
          ](/json-formatter/)
        

        /* Related Articles */
        
          ## Related Articles
          
            [
              ### JSON Formatting Guide
              Learn proper JSON indentation, validation, and common syntax errors.

            ](/blog/json-formatting-guide/)
            [
              ### JSON vs YAML vs XML
              Compare data formats: pros, cons, readability, and ecosystem support.

            ](/blog/json-vs-yaml-vs-xml/)
            [
              ### API Response Codes Best Practices
              HTTP status codes for REST APIs: 4xx vs 5xx, error formats, and patterns.

            ](/blog/api-response-codes-best-practices/)
            [
              ### Regex Patterns Cheat Sheet
              30+ copy-paste regex patterns for validation and data extraction.

            ](/blog/regex-patterns-cheat-sheet/)
          
        

      
    
  )

---

*Originally published on [bytepane.com](https://www.bytepane.com/blog/json-schema-validation-guide/). Visit for free interactive calculators and tools.*
