js-schema
js-schema is a new way of describing object schemas in JavaScript. It has a clean and simple syntax,
and it is capable of serializing to/from the popular JSON Schema format. Typical usecases include
object validation and random object generation.
A simple example
Defining a schema:
var schema = require('js-schema');
var Duck = schema({
quack : Function,
feed : Function,
age : Number.min(0).max(5),
color : ['yellow', 'brown']
});
The resulting function can be used for checking or validating objects:
var myDuck = { quack : function() {}, feed : function() {}, age : 2, color : 'yellow' };
var myCat = { purr : function() {}, feed : function() {}, age : 3, color : 'black' };
var animals = [myDuck, myCat, {}, ];
console.log( Duck(myDuck) );
console.log( Duck(myCat) );
console.log( animals.filter(Duck) );
console.log( animals.filter(schema({ feed : Function })) );
It is also possible to generate random objects for testing purposes:
console.log( Duck.generate() );
var testcases = Array.of(5, Duck).generate();
console.log(testcases);
Usage
The first parameter passed to the schema
function describes the schema, and the return value
is a new function called validator. Then the validator can be used to check any object against
the described schema as in the example above.
There are various patterns that can be used to describe a schema. For example,
schema({n : Number})
returns a validation function which returns true when called
with an object that has a number type property called n
. This is a combination of the
object pattern and the instanceof pattern. Most of the patterns are pretty intuitive, so
reading a schema description is quite easy even if you are not familiar with js-schema.
Most patterns accept other patterns as parameters, so composition of patterns is very easy.
Extensions are functions that return validator by themselves without using the schema
function
as wrapper. These extensions are usually tied to native object constructors, like Array
,
Number
, or String
, and can be used everywhere where a pattern is expected. Examples
include Array.of(X)
, Number.min(X)
.
For serialization to JSON Schema use the toJSON()
method of any schema. For deserialization
use schema.fromJSON(json)
. JSON Schema support is still incomplete, but it can reliably
deserialize JSON Schemas generated by js-schema itself.
Patterns
Basic rules
There are 9 basic rules used for describing schemas:
Class
(where Class
is a function, and has a function type property called schema
)
matches x
if Class.schema(x) === true
.Class
(where Class
is a function) matches x
if x instanceof Class
./regexp/
matches x
if /regexp/.test(x) === true
.[object]
matches x
if x
is deep equal to object
[pattern1, pattern2, ...]
matches x
if any of the given patterns match x
.{ 'a' : pattern1, 'b' : pattern2, ... }
matches x
if pattern1
matches x.a
,
pattern2
matches x.b
, etc. For details see the object pattern subsection.undefined
matches x
if x
is not null
or undefined
.null
matches x
if x
is null
or undefined
.primitive
(where primitive
is boolean, number, or string) matches x
if primitive === x
.
The order is important. When calling schema(pattern)
, the rules are examined one by one,
starting with the first. If there's a match, js-schema first resolves the sub-patterns, and then
generates the appropriate validator function and returns it.
Example
The following example contains patterns for all of the rules. The comments
denote the number of the rules used and the nesting level of the subpatterns (indentation).
var Color = function() {}, x = { };
var validate = schema({
a : [ Color, 'red', 'blue', [[0,0,0]] ],
b : Number,
c : /The meaning of life is \d+/,
d : undefined,
e : null
});
console.log( validate(x) );
validate(x)
returns true if all of these are true:
x.a
is either 'red', 'blue', an instance of the Color class,
or an array that is exactly like [0,0,0]
x.b
conforms to Number.schema (it return true if x.b instanceof Number
)x.c
is a string that matches the /The meaning of life is \d+/ regexpx
does have a property called d
x
doesn't have a property called e
, or it does but it is null
or undefined
The object pattern
The object pattern is more complex than the others. Using the object pattern it is possible to
define optional properties, regexp properties, etc. This extra information can be encoded in
the property names.
The property names in an object pattern are always regular expressions, and the given schema
applies to instance properties whose name match this regexp. The number of expected matches can
also be specified with ?
, +
or *
as the first character of the property name. ?
means
0 or 1, *
means 0 or more, and +
means 1 or more. A single *
as a property name
matches any instance property that is not matched by other regexps.
An example of using these:
var x = { };
var validate = schema({
'name' : String,
'colou?r' : String
'?location' : String,
'*identifier-.*' : Number,
'+serialnumber-.*' : Number,
'*' : Boolean
});
assert( validate(x) === true );
Extensions
Objects
Object.reference(object)
matches x
if x === object
.
Object.like(object)
matches x
if x
deep equals object
.
Functions
Function.reference(func)
matches x
if x === func
.
Arrays
The Array.like(array)
matches x
if x instanceof Array
and it deep equals array
.
The Array.of
method has three signatures:
Array.of(pattern)
matches x
if x instanceof Array
and pattern
matches every element of x
.Array.of(length, pattern)
additionally checks the length of the instance and returns true only if it equals to length
.Array.of(minLength, maxLength, pattern)
is similar, but checks if the length is in the given interval.
Numbers
There are five functions that can be used for describing number ranges: min
, max
, below
,
above
and step
. All of these are chainable, so for example Number.min(a).below(b)
matches x
if a <= x && x < b
. The Number.step(a)
matches x
if x
is a divisble by a
.
Strings
The String.of
method has three signatures:
String.of(charset)
matches x
if it is a string and contains characters that are included in charset
String.of(length, charset)
additionally checks the length of the instance and returns true only if it equals to length
.String.of(minLength, maxLength, charset)
is similar, but checks if the length is in the given interval.
charset
must be given in a format that can be directly inserted in a regular expression when
wrapped by []
. For example, 'abc'
means a character set containing the first 3 lowercase letters
of the english alphabet, while 'a-zA-C'
means a character set of all english lowercase letters,
and the first 3 uppercase letters. If charset
is undefined
then the a-zA-Z0-9
character set
is used.
Future plans
Better JSON Schema support. js-schema should be able to parse any valid JSON schema and generate
JSON Schema for most of the patterns (this is not possible in general, because of patterns that hold
external references like the 'instanceof' pattern).
Using the random object generation, it should be possible to build a QucikCheck-like testing
framework, which could be used to generate testcases for js-schema (yes, I like resursion).
Defining and validating resursive data structures:
var Tree = schema({ left : [Tree, Number], right : [Tree, Number] });
console.log( Tree({left : {left : 1, right : 2 }, right : 9}) );
console.log( Tree({left : {left : 1, right : null}, right : 9}) );
Contributing
Feel free to open an issue if you would like to help imporving js-schema or find a bug.
Installation
Using npm:
npm install js-schema
License
The MIT License
Copyright (C) 2012 Gábor Molnár gabor.molnar@sch.bme.hu