- A schema validator instanceExample:
`javascript
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())
});
`$3
Validates data against the schema.
Parameters:
-
data (any): The data to validate
- options (object, optional): Validation options
- setDefault (boolean): Add default values for undefined properties
- removeAdditional (boolean): Remove properties not allowed by additionalProperties
- context (string): Set to 'read' to remove writeOnly properties, 'write' to remove readOnly propertiesReturns: The validated and potentially modified data
Throws:
ValidationError if validation failsExamples:
`javascript
// 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
$3
`javascript
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: 'user@example.com',
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);
}
`$3
`javascript
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();
}
});
`$3
`javascript
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: 'user@example.com',
password: 'secretpassword',
createdAt: '2023-01-01T00:00:00Z' // This will be removed
};
const forCreation = await apiSchema.validate(createData, { context: 'write' });
console.log(forCreation); // { email: 'user@example.com', password: 'secretpassword' }
// When returning user data (read context) - password is removed, read-only fields are kept
const responseData = {
id: '123',
email: 'user@example.com',
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: 'user@example.com', createdAt: '...', updatedAt: '...' }
`$3
`javascript
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);
`$3
`javascript
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' }); // ✗
`$3
`javascript
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);
`$3
`javascript
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 definitions
`Extensibility
jsonpolice supports custom validators through class extension. You can extend the
StaticSchema class to add custom validation keywords:`javascript
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 ValidationError
`Key points for custom validators:
- Extend
StaticSchema (not Schema)
- 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 schemaThis 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:
`javascript
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:
$3
- type - Validate basic types (string, number, integer, boolean, array, object, null)
- enum - Validate against enumerated values
- const - Validate against a constant value$3
- minLength, maxLength - String length constraints
- pattern - Regular expression pattern matching
- format - 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$3
- minimum, maximum - Numeric range validation
- exclusiveMinimum, exclusiveMaximum - Exclusive numeric ranges
- multipleOf - Multiple validation$3
- items - Validate array items against schema(s)
- additionalItems - Handle additional items beyond defined schemas
- minItems, maxItems - Array length constraints
- uniqueItems - Ensure array items are unique
- contains - At least one item must match schema$3
- properties - Define property schemas
- patternProperties - Properties matching regex patterns
- additionalProperties - Handle additional properties
- required - Required properties
- minProperties, maxProperties - Object size constraints
- dependencies - Property dependencies (Draft 7)
- propertyNames - Validate property names$3
- allOf - Must match all schemas
- anyOf - Must match at least one schema
- oneOf - Must match exactly one schema
- not - Must not match schema
- if/then/else - Conditional validation$3
- $ref - Reference resolution
- $id - Schema identification
- definitions - Schema definitions (Draft 7)
- $defs - Schema definitions (2019-09+, preferred over definitions)$3
- title - Schema title for documentation
- description - Schema description for documentation
- default - Default values for properties
- examples - Example values for documentation
- readOnly - Properties that should not be sent in requests
- writeOnly - Properties that should not be sent in responses
- deprecated - Mark properties as deprecated (2019-09+)$3
- contentEncoding - Describe string content encoding (e.g., base64)
- contentMediaType - Describe string content media type (e.g., application/json)$3
- dependentSchemas - Schema-based dependencies (replaces object-form dependencies)
- dependentRequired - Property-based dependencies (replaces array-form dependencies)
- unevaluatedProperties - Handle properties not evaluated by other keywords
- unevaluatedItems - Handle array items not evaluated by other keywords
- Automatic version detection from $schema property
- Full backwards compatibility with Draft 7 schemasJSON Schema Version Support
jsonpolice automatically detects the JSON Schema version from the
$schema property:`javascript
// 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:
`typescript
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
1. Reuse schema instances - Create schemas once and reuse them for multiple validations
2. Use shared registries - Share registries between related schemas to cache external references
3. Optimize external references - Implement efficient retriever functions with caching
4. Consider validation options - Only use
setDefault, removeAdditional, and context when needed
5. Deep cloning optimization - The library automatically optimizes cloning for primitives vs objects in anyOf/oneOf validationsCompatibility
$3
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:
1. Dynamic import (recommended):
`javascript
// CommonJS file
const createSchema = async () => {
const { create } = await import('jsonpolice');
const schema = await create({ type: 'string' });
return schema;
};
`2. Convert your project to ESM by adding
"type": "module" to your package.json3. Use a bundler like webpack or esbuild that can handle ESM dependencies
$3
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:
`bash
Install dependencies
pnpm installRun tests
pnpm testCheck code coverage (must be 100%)
pnpm run cover
pnpm run check-coverageBuild the project
pnpm run buildClean build artifacts
pnpm run clean
``Development 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