@jarenjs/validate
v0.9.2
Published
Jaren is a JavaScript JSON Schema Validator
Downloads
178
Maintainers
Readme
@jarenjs/validate
The JSON Schema validating compiler at the heart of Jaren. It compiles JSON Schemas into optimized validation functions and fully supports draft-06, draft-07, draft 2019-09 and draft 2020-12 — passing 100% of the official JSON-Schema-Test-Suite for draft-07, 2019-09 and 2020-12.
Usage
import { JarenValidator } from '@jarenjs/validate';
const jaren = new JarenValidator();
const validate = jaren.compile({
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'integer', minimum: 0 }
},
required: ['name']
});
validate({ name: 'John', age: 30 }); // trueKey entry points:
new JarenValidator(options)— create a validator instance.addSchema(schema, key)— register schemas for$refresolution.addMetaSchema(schemas, key)— register (custom) meta-schemas, honoring$vocabulary.addFormats(formats)— register format validators (see@jarenjs/formats).compile(schema)— compile a schema into a validation function
Highlights:
- Annotation-based
unevaluatedProperties/unevaluatedItems - Spec-compliant dynamic scope for
$dynamicRef/$dynamicAnchorand$recursiveRef/$recursiveAnchor - Per-document draft handling for cross-draft references
$vocabulary-aware keyword selection and per-draftformat/content assertion defaults- Instance-data references via the
datakeyword (json-everything data-ref) and Ajv-style$data - Cross-field assertions via the
$queryextension keyword (a Jaren JSON Query inside the schema)
🔑 JSON Schema validation keywords
Jaren supports the full set of JSON Schema validation keywords. Here's a quick overview:
- JSON data type:
type,nullable,required - Numbers:
maximum,minimum,multipleOf - Strings:
maxLength,minLength,pattern - Content:
contentEncoding,contentMediaType - Arrays:
maxItems,minItems,uniqueItems,items,prefixItems,contains,unevaluatedItems - Objects:
maxProperties,minProperties,required,properties,patternProperties,unevaluatedProperties - All types:
enum,const - Compound:
not,oneOf,anyOf,allOf,if/then/else - Meta:
$schema,$id,$ref,$anchor,$dynamicRef/$dynamicAnchor,$recursiveRef/$recursiveAnchor,$vocabulary - Non-standard:
data(json-everything's data-ref proposal), Ajv-style$datareferences, and the$querykeyword below
🔑 JSON data type
- type
- nullable | (OpenAPI)
- required | as boolean (OpenAPI)
🔑 Keywords for numbers
- maximum / minimumor exclusiveMaximum / exclusiveMinimum
- multipleOf
BigInt instance values are supported too: maximum/minimum/exclusiveMaximum/exclusiveMinimum and multipleOf compile dedicated BigInt comparators when the data is a bigint.
🔑 Keywords for strings
- maxLength / minLength
- pattern
🔑 Keywords for content
- contentEncoding | asserts in
draft7, annotation-only fromdraft2019on (spec default), controlled by thecontentValidationoption - contentMediaType | same assertion defaults;
application/jsoncontent is parse-checked - ❌ contentSchema | annotation only (never asserted)
🔑 Keywords for format
- format
- formatMinimum / formatMaximumor formatExclusiveMinimum / formatExclusiveMaximum
🔑 Keywords for array
- maxItems / minItems
- uniqueItems
- items
- items | as schema or tuple deprecated in
draft2020 - items | as schema only new
draft2020
- items | as schema or tuple deprecated in
- prefixItems | as tuple new
draft2020 - additionalItems | as schema deprecated in
draft2020 - contains
- maxContains / minContains | new
draft2019 - unevaluatedItems | new
draft2019
🔑 Keywords for object
- maxProperties / minProperties
- required | as array!
- properties
- patternProperties
- additionalProperties
- dependencies | deprecated in
draft2019 - dependentRequired | new
draft2019 - dependentSchemas | new
draft2019 - propertyNames
- unevaluatedProperties | new
draft2019 - ❌ propertyDependencies
🔑 Keywords for all types
- enum
- const
🔑 Compound keywords
- not
- oneOf
- anyOf
- allOf
- if / then / else
See also:
🔑 Meta keywords
- $schema | used for draft detection, vocabulary selection and cross-draft references
- $id
- $ref
- $anchor
- $recursiveRef | new
draft2019& deprecated indraft2020 - $recursiveAnchor | new
draft2019& deprecated indraft2020 - $dynamicRef | new
draft2020 - $dynamicAnchor | new
draft2020 - $data | (Ajv specific)
- $vocabulary | new
draft2019- a custom metaschema that omits the validation vocabulary turns keywords liketypeandminimuminto annotations; a metaschema that declares theformat-assertionvocabulary turns format assertion on
🔑 Non-standard keywords
data| json-everything's data-ref proposalThe
datakeyword allows you to reference values from the instance being validated, enabling dynamic constraints based on other parts of the data.Example - Requiring B >= A:
{ "type": "object", "properties": { "A": { "type": "number" }, "B": { "type": "number", "data": { "minimum": "/A" } } } }- Passes:
{ "A": 5, "B": 10 }(10 >= 5) - Fails:
{ "A": 15, "B": 10 }(10 < 15)
Example - Enum from instance array:
{ "type": "object", "properties": { "color": { "data": { "enum": "/validColors" } }, "validColors": { "type": "array", "items": { "type": "string" } } } }Supported keywords within
data:- Number constraints:
minimum,maximum,exclusiveMinimum,exclusiveMaximum,multipleOf - String constraints:
minLength,maxLength,pattern,format - Array constraints:
minItems,maxItems - Object constraints:
minProperties,maxProperties - Value constraints:
enum,const
Both absolute JSON Pointers (e.g.,
/A,/limits/min) and relative JSON Pointers (e.g.,0/parent,1/sibling) are supported.- Passes:
$data| Ajv-style instance references ({ "minimum": { "$data": "1/limit" } }) — supports every keyword thedatalist above supports, plusuniqueItemsandrequired. Refs compile once at schema compile time through the compiled pointer engine of@jarenjs/json.$query| Jaren's cross-field assertion keyword — see below
🔑 Miscellaneous keywords
- ❌ strict
- ❌ strictFormat
- ❌ strictTuple
- ❌ errorMessage
- definitions | used by initial schema traversal deprecated in
draft2019 - $defs | used by initial schema traversal new
draft2019 - components | (OpenAPI)
🛠️ Notable capabilities
👉 Modelling Inheritance with JSON Schema
Jaren fully supports unevaluatedProperties, so the inheritance patterns from the Modelling Inheritance blog post work out of the box. Annotations flow from properties, patternProperties, additionalProperties and every in-place applicator (allOf/anyOf/oneOf/if-then-else/$ref/dependentSchemas), with annotations from failed branches correctly discarded.
See also:
👉 Express array constraints more cleanly
Jaren fully supports unevaluatedItems, covering the array patterns from the 2020-12 release notes: items evaluated by items, prefixItems, additionalItems and (in 2020-12) contains are tracked, and everything left over is validated by the unevaluatedItems schema.
👉 Using Dynamic References to Support Generic Types
Jaren fully supports $dynamicRef/$dynamicAnchor (2020-12) and $recursiveRef/$recursiveAnchor (2019-09), including the generic-type patterns from the dynamicRef and generics blog post. Resolution follows the specification's dynamic-scope rules: entering a schema resource brings all of its dynamic anchors into scope, and a $dynamicRef resolves to the anchor in the outermost resource of the dynamic scope.
See also:
👉 Runtime schema manipulation of constraints
Jaren supports the data-ref proposal from json-everything through the data keyword, plus Ajv-style $data references. Both allow a schema constraint to take its value from the instance being validated:
- Absolute JSON Pointers (e.g.,
/A,/limits/min) - Relative JSON Pointers (e.g.,
0/parent,1/sibling) - All common constraint keywords:
minimum,maximum,exclusiveMinimum,exclusiveMaximum,multipleOf,minLength,maxLength,pattern,format,enum,const,minItems,maxItems,minProperties,maxProperties
The refs compile once at schema compile time through the compiled pointer engine of @jarenjs/json and resolve allocation-free per validation.
See also:
👉 Vocabularies and cross-draft references
A schema's $schema declaration is honored per document: referenced documents that declare a different draft are processed with that draft's keyword set (a draft-07 document ignores dependentRequired; a 2019-09 document ignores prefixItems). Custom metaschemas with $vocabulary are respected — omitting the validation vocabulary turns validation keywords into annotations, and declaring format-assertion turns format assertion on.
$query — cross-field assertions
$query is a Jaren extension keyword: its value is a
Jaren JSON Query document, compiled once at
schema compile time and evaluated per validation against the current instance
location. The instance is valid when the query result's
effective boolean value
is true. This gives JSON Schema the class of constraint it is notoriously bad
at — cross-field arithmetic, ordering, aggregate consistency, quantification —
through the query engine that already sits underneath the stack. Other
validators treat $query as an unknown-keyword annotation, so schemas using
it stay portable; the constraint simply only asserts here.
An invoice whose total must equal the sum of its line amounts:
const validate = jaren.compile({
type: 'object',
properties: {
lines: { type: 'array', items: { type: 'object' } },
total: { type: 'number' }
},
$query: { $eq: ['$.total', { $sum: '$.lines[*].amount' }] }
});
validate({ lines: [{ amount: 12.5 }, { amount: 7.5 }], total: 20 }); // true
validate({ lines: [{ amount: 12.5 }, { amount: 7.5 }], total: 21 }); // falseDate ordering ($le compares strings by code points, QUERY-FORMAT §8.4 —
exactly right for ISO dates):
jaren.compile({ $query: { $le: ['$.start', '$.end'] } });Quantification over items:
jaren.compile({
$query: { $every: { l: '$.lines[*]' }, $satisfies: { $gt: ['$l.qty', 0] } }
});Two external parameters are bound on every evaluation: root — the instance
root — and path — the current instance location as a JSON pointer string.
(The JSLT template layer reserves the same two names with one twist: its
matching language is JSONPath, so its path is an RFC 9535 normalized
path, not a pointer — see JSLT-FORMAT §8.2.)
So a subschema can reach across the document:
jaren.compile({
type: 'object',
properties: {
lines: {
type: 'array',
items: {
type: 'object',
// every line's currency must match the document-level currency
$query: { $eq: ['$.currency', '$root.currency'] }
}
}
}
});Semantics and composition:
$queryworks at any subschema level and composes like every other keyword: underproperties/itemsthe query's$is that location's value,$pathits pointer (/lines/0, ...),$rootthe whole document.- A bare JSONPath string is the degenerate query:
{ "$query": "$.approved" }asserts the EBV of that member (missing → empty sequence → false). - Query runtime errors (
JQ2xxx— e.g. arithmetic on a non-number, the EBV of a multi-item result) are validation failures, never throws; incollectErrorsmode the error params carry thecodeand the querydocPath. Malformed query documents and free externals other thanroot/pathfail fast atcompile(). - Schema literals inside the query (
$valid/$assert/$as, QUERY-FORMAT §8.11) compile against the same validator instance, so their$refs resolve to youraddSchemaregistrations.
Development
Unit tests live in test/validate/ at the repository root (npm run test:validate). Performance against Ajv is measured over the official test suite with the benchmark workspace (node benchmark/profiler.js --profile-all), which also houses the test-failure debugger, coverage and call-graph tools.
This package's internals — the four-phase compile pipeline, ref flattening, annotation tracking, dynamic scope — are described in its own ARCHITECTURE document. For practical usage recipes (options, lightweight setups, custom formats, pitfalls) see the repository HOWTO; for the monorepo picture see the repository README and ARCHITECTURE; for what is planned next see the ROADMAP.
