jsonpolice
v13.0.0
Published
JSON Schema parser and validator
Downloads
2,569
Readme
jsonpolice
A powerful JavaScript library implementing JSON Schema draft 7 specification with support for newer versions. Validate JSON data against schemas with comprehensive validation rules, default value assignment, and property filtering capabilities.
Features
- ✅ JSON Schema Draft 7, 2019-09 & 2020-12: Full implementation with automatic version detection
- ✅ Schema Validation: Comprehensive validation with detailed error reporting
- ✅ Default Values: Automatic assignment of default values for undefined properties
- ✅ Property Filtering: Remove additional, readOnly, or writeOnly properties
- ✅ Context-Aware: Support for read/write contexts to handle property visibility
- ✅ External References: Resolve
$refreferences to external schemas - ✅ Modern Keywords: Support for
dependentSchemas,dependentRequired,unevaluatedProperties,unevaluatedItems, and$defs - ✅ Backwards Compatible: Full compatibility with existing Draft 7 schemas
- ✅ TypeScript Support: Full TypeScript definitions included
- ✅ Modern ES Modules: Pure ESM package for modern JavaScript environments
- ✅ Minimal Dependencies: Lightweight with minimal dependencies (jsonref and lodash)
- ✅ Extensible: Support for custom validators via class extension
Installation
# npm
npm install jsonpolice
# pnpm
pnpm add jsonpolice
# yarn
yarn add jsonpoliceRequirements:
- Node.js >= 18.17.0
- ES Module support (this is a pure ESM package)
Quick Start
Basic Schema Validation
import { create } from 'jsonpolice';
const schema = await create({
type: 'object',
properties: {
name: { type: 'string', minLength: 1 },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0, maximum: 120 }
},
required: ['name', 'email']
});
// Valid data
const validData = {
name: 'John Doe',
email: '[email protected]',
age: 30
};
try {
const result = await schema.validate(validData);
console.log('Valid:', result);
} catch (error) {
console.error('Validation failed:', error.message);
}Schema with Default Values
import { create } from 'jsonpolice';
const schema = await create({
type: 'object',
properties: {
name: { type: 'string' },
role: { type: 'string', default: 'user' },
active: { type: 'boolean', default: true },
credits: { type: 'number', default: 100 }
}
});
const data = { name: 'Alice' };
const result = await schema.validate(data, { setDefault: true });
console.log(result);
// Output: { name: 'Alice', role: 'user', active: true, credits: 100 }Exports
jsonpolice exports the following members:
// Main function
import { create } from 'jsonpolice';
// Classes
import { Schema, StaticSchema } from 'jsonpolice';
// Types
import { SchemaOptions, ValidationOptions, SchemaVersion } from 'jsonpolice';
// Errors
import { ValidationError, SchemaError } from 'jsonpolice';
// Re-exports from jsonref
import { expand, parse, resolve } from 'jsonpolice';
// Default export
import create from 'jsonpolice'; // Same as named exportAPI Reference
create(schemaOrUri, options?)
Creates a new schema validator instance.
Parameters:
schemaOrUri(object | string): JSON Schema object or URI to fetch the schemaoptions(object, optional): Configuration optionsversion(string): Explicit JSON Schema version ('draft-07', '2019-09', '2020-12'). Auto-detected from$schemaif not providedscope(string): Base URI for resolving relative references (optional for inline schemas)registry(object): Cache object to store resolved references for reuseretriever(function): Function to fetch external references(url: string) => Promise<object>
Returns: Promise<Schema> - A schema validator instance
Example:
import { create } from 'jsonpolice';
// Create from schema object
const schema = await create({
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' }
}
});
// Create from URI with custom retriever
const remoteSchema = await create('https://example.com/schema.json', {
retriever: (url) => fetch(url).then(r => r.json())
});schema.validate(data, options?)
Validates data against the schema.
Parameters:
data(any): The data to validateoptions(object, optional): Validation optionssetDefault(boolean): Add default values for undefined propertiesremoveAdditional(boolean): Remove properties not allowed byadditionalPropertiescontext(string): Set to'read'to remove writeOnly properties,'write'to remove readOnly properties
Returns: The validated and potentially modified data
Throws: ValidationError if validation fails
Examples:
// Basic validation
const result = await schema.validate({ name: 'John' });
// Validation with default values
const withDefaults = await schema.validate(
{ name: 'John' },
{ setDefault: true }
);
// Remove additional properties
const cleaned = await schema.validate(
{ name: 'John', extra: 'removed' },
{ removeAdditional: true }
);
// Context-aware validation (API response context)
const forReading = await schema.validate(
{ password: 'secret', publicInfo: 'visible' },
{ context: 'read' }
);Usage Examples
Complex Schema Validation
import { create } from 'jsonpolice';
const userSchema = await create({
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
email: { type: 'string', format: 'email' },
name: { type: 'string', minLength: 1, maxLength: 100 },
age: { type: 'integer', minimum: 0, maximum: 150 },
roles: {
type: 'array',
items: { type: 'string', enum: ['admin', 'user', 'guest'] },
uniqueItems: true
},
profile: {
type: 'object',
properties: {
bio: { type: 'string', maxLength: 500 },
website: { type: 'string', format: 'uri' },
socialMedia: {
type: 'object',
additionalProperties: { type: 'string', format: 'uri' }
}
}
}
},
required: ['id', 'email', 'name'],
additionalProperties: false
});
const userData = {
id: '123e4567-e89b-12d3-a456-426614174000',
email: '[email protected]',
name: 'John Doe',
age: 30,
roles: ['user'],
profile: {
bio: 'Software developer',
website: 'https://johndoe.dev',
socialMedia: {
twitter: 'https://twitter.com/johndoe',
github: 'https://github.com/johndoe'
}
}
};
try {
const validated = await userSchema.validate(userData);
console.log('User data is valid:', validated);
} catch (error) {
console.error('Validation failed:', error.message);
}Working with External Schema References
import { create } from 'jsonpolice';
const schema = await create({
type: 'object',
properties: {
user: { '$ref': 'https://json-schema.org/learn/examples/person.schema.json' },
timestamp: { type: 'string', format: 'date-time' }
}
}, {
retriever: async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch schema: ${response.status}`);
}
return response.json();
}
});Context-Aware Validation (Read/Write Operations)
import { create } from 'jsonpolice';
const apiSchema = await create({
type: 'object',
properties: {
id: { type: 'string', readOnly: true },
email: { type: 'string', format: 'email' },
password: { type: 'string', writeOnly: true, minLength: 8 },
createdAt: { type: 'string', format: 'date-time', readOnly: true },
updatedAt: { type: 'string', format: 'date-time', readOnly: true }
},
required: ['email']
});
// When creating a user (write context) - password is allowed, read-only fields are removed
const createData = {
email: '[email protected]',
password: 'secretpassword',
createdAt: '2023-01-01T00:00:00Z' // This will be removed
};
const forCreation = await apiSchema.validate(createData, { context: 'write' });
console.log(forCreation); // { email: '[email protected]', password: 'secretpassword' }
// When returning user data (read context) - password is removed, read-only fields are kept
const responseData = {
id: '123',
email: '[email protected]',
password: 'secretpassword', // This will be removed
createdAt: '2023-01-01T00:00:00Z',
updatedAt: '2023-01-01T00:00:00Z'
};
const forResponse = await apiSchema.validate(responseData, { context: 'read' });
console.log(forResponse); // { id: '123', email: '[email protected]', createdAt: '...', updatedAt: '...' }Advanced Validation with Custom Formats
import { create } from 'jsonpolice';
const schema = await create({
type: 'object',
properties: {
username: {
type: 'string',
pattern: '^[a-zA-Z0-9_]{3,20}$'
},
birthDate: {
type: 'string',
format: 'date'
},
phoneNumber: {
type: 'string',
pattern: '^\\+?[1-9]\\d{1,14}$'
},
metadata: {
type: 'object',
patternProperties: {
'^[a-z_]+$': { type: 'string' }
},
additionalProperties: false
}
}
});
const data = {
username: 'john_doe_123',
birthDate: '1990-05-15',
phoneNumber: '+1234567890',
metadata: {
department: 'engineering',
team_lead: 'jane_smith'
}
};
const result = await schema.validate(data);Conditional Validation with if/then/else
import { create } from 'jsonpolice';
const schema = await create({
type: 'object',
properties: {
country: { type: 'string' },
postalCode: { type: 'string' }
},
required: ['country'],
if: {
properties: { country: { const: 'US' } }
},
then: {
properties: {
postalCode: { pattern: '^\\d{5}(-\\d{4})?$' }
}
},
else: {
properties: {
postalCode: { pattern: '^[A-Z0-9]{3,10}$' }
}
}
});
// Valid US postal code
await schema.validate({ country: 'US', postalCode: '12345' }); // ✓
// Valid non-US postal code
await schema.validate({ country: 'UK', postalCode: 'SW1A 1AA' }); // ✓
// Invalid - doesn't match US format
// await schema.validate({ country: 'US', postalCode: 'ABC' }); // ✗Schema Definitions with $defs
import { create } from 'jsonpolice';
const schema = await create({
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'object',
properties: {
billing_address: { $ref: '#/$defs/address' },
shipping_address: { $ref: '#/$defs/address' }
},
$defs: {
address: {
type: 'object',
properties: {
street: { type: 'string' },
city: { type: 'string' },
state: { type: 'string', pattern: '^[A-Z]{2}$' },
zipCode: { type: 'string', pattern: '^\\d{5}$' }
},
required: ['street', 'city', 'state', 'zipCode']
}
}
});
const orderData = {
billing_address: {
street: '123 Main St',
city: 'Boston',
state: 'MA',
zipCode: '02101'
},
shipping_address: {
street: '456 Oak Ave',
city: 'Cambridge',
state: 'MA',
zipCode: '02139'
}
};
const validated = await schema.validate(orderData);Performance Optimization with Shared Registry
import { create } from 'jsonpolice';
const registry = {}; // Shared registry for caching
const userSchema = await create(userSchemaDefinition, { registry });
const productSchema = await create(productSchemaDefinition, { registry });
const orderSchema = await create(orderSchemaDefinition, { registry });
// All schemas will share the same registry, improving performance
// when they reference common schema definitionsExtensibility
jsonpolice supports custom validators through class extension. You can extend the StaticSchema class to add custom validation keywords:
import { StaticSchema, ValidationError, Schema } from 'jsonpolice';
class CustomSchema extends StaticSchema {
// Override to register custom validator keywords
addCustomValidators(validators, version) {
validators.add('divisibleBy');
return validators;
}
// Implement the custom validator method
divisibleByValidator(data, spec, path, opts) {
if (typeof data !== 'number') {
return data;
}
if (data % spec.divisibleBy !== 0) {
throw new ValidationError(
path,
Schema.scope(spec),
'divisibleBy',
`must be divisible by ${spec.divisibleBy}`
);
}
return data;
}
// Override create to return the custom schema type
static async create(dataOrUri, opts = {}) {
const schema = new CustomSchema(dataOrUri, opts);
await schema.spec();
return schema;
}
}
// Use the custom schema
const schema = await CustomSchema.create({
type: 'number',
divisibleBy: 3
});
await schema.validate(9); // ✓ Valid
// await schema.validate(10); // ✗ Throws ValidationErrorKey points for custom validators:
- Extend
StaticSchema(notSchema) - Override
addCustomValidators(validators, version)to register keyword names - Implement validator methods with naming pattern:
{keyword}Validator(data, spec, path, opts) - Override
static create()to properly initialize your custom schema - Use
await schema.spec()in the create method to initialize the schema
This pattern allows you to:
- Add domain-specific validation rules
- Implement custom format validators
- Create specialized validators for your application needs
- Maintain type safety and error handling consistency
Error Handling
jsonpolice provides detailed error information when validation fails:
import { create } from 'jsonpolice';
const schema = await create({
type: 'object',
properties: {
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0 }
},
required: ['email']
});
try {
await schema.validate({
email: 'invalid-email',
age: -5
});
} catch (error) {
console.log(error.name); // 'ValidationError'
console.log(error.message); // Detailed error message
console.log(error.errors); // Array of specific validation errors
// Each error contains:
// - path: JSON Pointer to the invalid property
// - message: Human-readable error description
// - constraint: The violated constraint
// - value: The invalid value
}Supported JSON Schema Keywords
jsonpolice implements the complete JSON Schema Draft 7 specification:
Type Validation
type- Validate basic types (string, number, integer, boolean, array, object, null)enum- Validate against enumerated valuesconst- Validate against a constant value
String Validation
minLength,maxLength- String length constraintspattern- Regular expression pattern matchingformat- Built-in format validation including:- Date/Time:
date,time,date-time - Email:
email,idn-email - Hostnames:
hostname,idn-hostname - IP Addresses:
ipv4,ipv6 - URIs:
uri,uri-reference,iri,iri-reference,uri-template - JSON Pointers:
json-pointer,relative-json-pointer - Other:
uuid,regex,semver
- Date/Time:
Number Validation
minimum,maximum- Numeric range validationexclusiveMinimum,exclusiveMaximum- Exclusive numeric rangesmultipleOf- Multiple validation
Array Validation
items- Validate array items against schema(s)additionalItems- Handle additional items beyond defined schemasminItems,maxItems- Array length constraintsuniqueItems- Ensure array items are uniquecontains- At least one item must match schema
Object Validation
properties- Define property schemaspatternProperties- Properties matching regex patternsadditionalProperties- Handle additional propertiesrequired- Required propertiesminProperties,maxProperties- Object size constraintsdependencies- Property dependencies (Draft 7)propertyNames- Validate property names
Schema Composition
allOf- Must match all schemasanyOf- Must match at least one schemaoneOf- Must match exactly one schemanot- Must not match schemaif/then/else- Conditional validation
Meta-Schema Support
$ref- Reference resolution$id- Schema identificationdefinitions- Schema definitions (Draft 7)$defs- Schema definitions (2019-09+, preferred overdefinitions)
Metadata Keywords
title- Schema title for documentationdescription- Schema description for documentationdefault- Default values for propertiesexamples- Example values for documentationreadOnly- Properties that should not be sent in requestswriteOnly- Properties that should not be sent in responsesdeprecated- Mark properties as deprecated (2019-09+)
Content Keywords
contentEncoding- Describe string content encoding (e.g., base64)contentMediaType- Describe string content media type (e.g., application/json)
New Features in JSON Schema 2019-09 & 2020-12
dependentSchemas- Schema-based dependencies (replaces object-formdependencies)dependentRequired- Property-based dependencies (replaces array-formdependencies)unevaluatedProperties- Handle properties not evaluated by other keywordsunevaluatedItems- Handle array items not evaluated by other keywords- Automatic version detection from
$schemaproperty - Full backwards compatibility with Draft 7 schemas
JSON Schema Version Support
jsonpolice automatically detects the JSON Schema version from the $schema property:
// Draft 7 (default)
const draft7Schema = await create({
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'string'
});
// JSON Schema 2019-09
const schema2019 = await create({
$schema: 'https://json-schema.org/draft/2019-09/schema',
type: 'object',
dependentRequired: {
name: ['surname']
}
});
// JSON Schema 2020-12
const schema2020 = await create({
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'object',
properties: {
name: { type: 'string' }
},
unevaluatedProperties: false
});
// Explicit version
const explicitSchema = await create({
type: 'string'
}, { version: '2020-12' });TypeScript Support
Full TypeScript definitions are included:
import { create, Schema, ValidationError } from 'jsonpolice';
interface User {
id: string;
email: string;
name: string;
}
const schema: Schema = await create({
type: 'object',
properties: {
id: { type: 'string' },
email: { type: 'string', format: 'email' },
name: { type: 'string' }
},
required: ['id', 'email', 'name']
});
try {
const validated: User = await schema.validate(data);
} catch (error: ValidationError) {
console.error('Validation failed:', error.message);
}Performance Tips
- Reuse schema instances - Create schemas once and reuse them for multiple validations
- Use shared registries - Share registries between related schemas to cache external references
- Optimize external references - Implement efficient retriever functions with caching
- Consider validation options - Only use
setDefault,removeAdditional, andcontextwhen needed - Deep cloning optimization - The library automatically optimizes cloning for primitives vs objects in
anyOf/oneOfvalidations
Compatibility
Node.js
jsonpolice requires Node.js >= 18.17.0 and is published as a pure ESM package.
Using with CommonJS:
If you need to use jsonpolice in a CommonJS project, you have several options:
- Dynamic import (recommended):
// CommonJS file
const createSchema = async () => {
const { create } = await import('jsonpolice');
const schema = await create({ type: 'string' });
return schema;
};Convert your project to ESM by adding
"type": "module"to your package.jsonUse a bundler like webpack or esbuild that can handle ESM dependencies
Browser Support
jsonpolice works in all modern browsers that support:
- ES2022+ features
- ES Modules (ESM)
- Promise and async/await
- JSON.parse/JSON.stringify
Supported environments:
- Node.js >= 18.17.0
- Chrome/Edge >= 91
- Firefox >= 89
- Safari >= 15
- Modern bundlers (webpack, vite, rollup, esbuild)
License
MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please ensure all tests pass and maintain 100% code coverage:
# Install dependencies
pnpm install
# Run tests
pnpm test
# Check code coverage (must be 100%)
pnpm run cover
pnpm run check-coverage
# Build the project
pnpm run build
# Clean build artifacts
pnpm run cleanDevelopment Guidelines:
- Maintain 100% test coverage for all new code
- Follow existing code style and conventions
- Add tests for bug fixes and new features
- Update documentation for API changes
- Ensure all tests pass before submitting PRs
Resources
- GitHub Repository: vivocha/jsonpolice
- npm Package: jsonpolice
- Issue Tracker: GitHub Issues
- JSON Schema Specification: json-schema.org
- JSON Schema Draft 7: Specification
- JSON Schema 2019-09: Specification
- JSON Schema 2020-12: Specification
