Git Product home page Git Product logo

js-schema's Introduction

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. The typical use case is declarative object validation.

Latest release: 1.0.1 (2015/05/06)

Features

Defining a schema:

var Duck = schema({              // A duck
  swim : Function,               //  - can swim
  quack : Function,              //  - can quack
  age : Number.min(0).max(5),    //  - is 0 to 5 years old
  color : ['yellow', 'brown']    //  - has either yellow or brown color
});

The resulting function (Duck) can be used to check objects against the declared schema:

// Some animals
var myDuck = { swim : function() {}, quack : function() {}, age : 2, color : 'yellow' },
    myCat =  { walk : function() {}, purr  : function() {}, age : 3, color : 'black'  },
    animals = [ myDuck, myCat, {}, /*...*/ ];

// Simple checks
console.log( Duck(myDuck) ); // true
console.log( Duck(myCat)  ); // false

// Using the schema function with filter
var ducks   = animals.filter( Duck );                        // every Duck-like animal
var walking = animals.filter( schema({ walk : Function }) ); // every animal that can walk

It is also possible to define self-referencing data structures:

var Tree = schema({ left : [ Number, Tree ], right : [ Number, Tree ] });

console.log( Tree({ left : 3, right : 3 })                        ); // true
console.log( Tree({ left : 3, right : { left: 5, right: 5 } })    ); // true
console.log( Tree({ left : 3, right : { left: 5, right: 's' } })  ); // false

Error reporting:

Duck.errors({
  swim: function() {},
  quack: function() {},
  age: 6,
  color: 'green'
});

// {
//   age: 'number = 6 is bigger than required maximum = 5',
//   color: 'string = green is not reference to string = yellow AND
//           string = green is not reference to string = brown'
// }

Usage

Include js-schema in your project with var schema = require('js-schema'); in node.js or with <script src="js-schema.min.js"></script> in the browser. AMD module loading is also supported.

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 (it returns an object) or call JSON.stringify(x) on the schema (to get a string). 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 10 basic rules used for describing schemas:

  1. Class (where Class is a function, and has a function type property called schema) matches x if Class.schema(x) === true.
  2. Class (where Class is a function) matches x if x instanceof Class.
  3. /regexp/ matches x if /regexp/.test(x) === true.
  4. [object] matches x if x is deep equal to object
  5. [pattern1, pattern2, ...] matches x if any of the given patterns match x.
  6. { 'a' : pattern1, 'b' : pattern2, ... } matches x if pattern1 matches x.a, pattern2 matches x.b, etc. For details see the object pattern subsection.
  7. primitive (where primitive is boolean, number, or string) matches x if primitive === x.
  8. null matches x if x is null or undefined.
  9. undefined matches anything.
  10. schema.self references the schema returned by the last use of the schema function. For details see the self-referencing subsection.

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({                    // (6) 'object' pattern
  a : [ Color, 'red', 'blue', [[0,0,0]] ], //     (5) 'or' pattern
                                           //         (2) 'instanceof' pattern
                                           //         (7) 'primitive' pattern
                                           //         (4) 'deep equality' pattern
  b : Number,                              //     (1) 'class schema' pattern
  c : /The meaning of life is \d+/,        //     (3) 'regexp' pattern
  d : undefined,                           //     (9) 'anything' pattern
  e : [null, schema.self]                  //     (5) 'or' pattern
                                           //         (8) 'nothing' pattern
                                           //         (10) 'self' pattern
});

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+/ regexp
  • x doesn't have a property called e, or it does but it is null or undefined, or it is an object that matches this schema

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,  // x.name must be string
  'colou?r'          : String   // x must have a string type property called either
                                // 'color' or 'colour' but not both
  '?location'        : String,  // if x has a property called 'location' then it must be string
  '*identifier-.*'   : Number,  // if the name of a property of x matches /identifier-.*/ then
                                // it must be a number
  '+serialnumber-.*' : Number,  // if the name of a property of x matches /serialnumber-.*/ then
                                // it must be a number and there should be at least one such property
  '*'                : Boolean  // any other property that doesn't match any of these rules
                                // must be Boolean
});

assert( validate(x) === true );

Self-referencing

The easiest way to do self-referencing is using schema.self. However, to support a more intuitive notation (as seen in the Tree example above) there is an other way to reference the schema that is being described. When executing this:

var Tree = schema({ left : [ Number, Tree ], right : [ Number, Tree ] });

js-schema sees in fact { left : [ Number, undefined ], right : [ Number, undefined ] } as first parameter, since the value of the Tree variable is undefined when the schema function is called. Consider the meaning of [ Number, undefined ] according to the rules described above: 'this property must be either Number, or anything else'. It doesn't make much sense to include 'anything else' in an 'or' relation. If js-schema sees undefined in an or relation, it assumes that this is in fact a self-reference.

Use this feature carefully, because it may easily lead to bugs. Only use it when the return value of the schema function is assigned to a newly defined variable.

Extensions

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 divisible 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 or null then there's no restriction on the character set.

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.

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.

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).

Contributing

Feel free to open an issue or send a pull request if you would like to help improving js-schema or find a bug.

People who made significant contributions so far:

Installation

Using npm:

npm install js-schema

Using bower:

bower install js-schema

Build

To build the browser version you will need node.js and the development dependencies that can be installed with npm (type npm install ./ in the project directory). build.sh assembles a debug version using browserify and then minifies it using uglify.

License

The MIT License

Copyright (C) 2012 Gábor Molnár [email protected]

js-schema's People

Contributors

alanjames1987 avatar alex-ivanov avatar maxgfeller avatar parhelium avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

js-schema's Issues

Add AMD / RequireJS Support

I would like to be able to load JS-Schema via RequireJS. I will be adding this feature by modifying lib/schema.js and submitting a pull request soon.

Array.of() fails in browser when used in combination with prototypejs

prototypejs (http://prototypejs.org/) has an own implementation of Array.prototype.reverse:

  function reverse(inline) {
    return (inline === false ? this.toArray() : this)._reverse();
  }

with _reverse pointing to the original implementation.

Array.of() calls

var args = Array.prototype.reverse.call(arguments)

but arguments is not an object of type Array, thus doesn't have the _reverse method - and fails.

For a test, modified the minified version of js-schema to call

Array.prototype.slice.call(arguments).reverse()

and it worked. (as per http://www.sitepoint.com/arguments-a-javascript-oddity/ : "Convert it to a real Array")

I didn't check other uses or similar constructs, but so far the scripts of my site run again.
Side question: How do you create the minified version before doing a commit?

Add ability to generate base instance from schema

let foo = schema({
  foo: String,
  bar: Boolean,
  baz: Array.of(Number),
})

foo.generate()
// => {
//   foo: '',
//   bar: false,
//   baz: []
//

This is useful when using js-schema to back models. With this generate extension, it can be used to both validate the model and generate scaffolding to populate the model. The latter is useful for a case like:

class Foo {
  constructor (data) {
    _.assign(this, this.schema, data)
  }

  schema: schema({
    foo: String,
    bar: Number,
    baz: Function
  })
}

new Foo ({ foo: 'bar' })
// => {
//   foo: 'bar',
//   bar: 0,
//   baz: Noop
// }

reference another schema

Is there a way to reference another schema? Doing

var foo = schema(...);
var bar = schema({"foo": foo});

bar.toJSON()

gives a flattened schema: foo is incorporated into bar, rather than bar having a reference to foo. Is there a way, instead, to generate a reference to foo? The docs only cover references to self.

I couldn't get this to work with Node.js

In CoffeeScript:

schema = require "js-schema"
x = schema {
  # ...
}

In this case, schema exists, but x is undefined. If I try schema.wrap (a shot in the dark--the documentation wasn't helping and it's a bit thin), I get

TypeError: Object function () {} has no method 'wrap'

Any ideas?

How to validate only given properties exist?

How do we specify that any fields other than the ones specified are not allowed? SO, f.e., I want this:

var Thing = schema({
  foo: Number
})

Thing({foo: 5}) // true
Thing({foo: 5, bar: "blah"}) // false, because `blah` isn't a specified property.

Multiple Datatype Checking

It would be great to be able to check if a property of an object is one data type or another.

For example I would like to be able to check if something is a number between a certain range or just null.

Discrepancy in toJSON output between node and browser versions

Hi,

The toJSON method produces different result when run in the browser and run in node. I've put the example code and results in the following gist, if you can take a look: https://gist.github.com/4687493

The schema is in schema.js
The node script is convert.node.js
The 2 outputs are browser_output.json and node_output.json.

It looks like the parser is not navigating into the objects within an array (only returning [Object])

Let me know if there's any more info I can give.

Thanks,
Gary

serialize/deserialize schema causes validator to fail when Function type is used

No serialization. This works:

var Duck = schema({              // A duck
  swim : Function,               //  - can swim
});

var myDuck = { swim : function() {}, quack : function() {}, age : 2, color : 'yellow' };

Duck(myDuck);

Now, serialize then deserialize the Duck validator and the validator fails:

var Duck = schema({              // A duck
  swim : Function,               //  - can swim
});

var myDuck = { swim : function() {}, quack : function() {}, age : 2, color : 'yellow' };

//serialize
var objVersion = Duck.toJSON()

//deserialize
DuckReborn = schema.fromJSON(objVersion);

DuckReborn(myDuck);

I think this is because swim gets serialized as {} rather than as something that the fromJSON function can identify it as instanceof Function

I can tell you that the offending line of code seems to be in the toJSON function just a few lines after json.type = "object"; where it says json.properties[property.key] = property.value.toJSON();. Here, property.value.constructor is a function but the return value from property.value.toJSON() is an empty object

Autocorrect

Would it be possible for a value like channels must be an Array but instead is a string, but consider it okay and wrap the string in an array instead?

not present optional fields are reported as errors

var testSchema = schema({
'?optional': String
});

// validating should show the schema to be valid, this works
expect(testSchema({})).toBeTruthy();

// checking for errors should return false, but instead returns { optional : 'undefined is not a String' }
expect(testSchema.errors({})).toEqual(false);

Is this an abandoned project?

Hi,

looking through the issues and PR I get the impression that this project has been abandoned for a while.

If this is the case, Is there an active fork bringing the project still forward?

@molnarg Appreciate your work, thanks for sharing it.

Specifying array pattern

Hi,

I would like to make a schema to validate that all items in an array match an expression. Is there a way to do this in the current implementation?

Optional validation does not work anymore

The following schema

schema({
  a: [Function, String],
  b: String,
  c: Array.of(3, String),
  d: Array.of(3, String),
  e: [null, schema.self]
})

will never pass on object without the e field.

The issue it is on the latest, not the 0.6.2

Future of this project?

There are 4 outstanding PRs that seem quite old, and apparently no activity for almost two years.

Is this orphanware, has further development moved somewhere else, or is the hiatus just temporary?

typescript TypeError: js_schema_1.default is not a function

Following TypeScript code

import {Schema} from 'js-schema';
import schema from 'js-schema';

let obj: Schema = schema({ foo: String, bar: String });

is transpiled into followin JS code

var js_schema_1 = require('js-schema');
var obj = js_schema_1.default({ foo: String, bar: String });

which leads to the error

TypeError: js_schema_1.default is not a function

How do I prevent transpiling this default-property?

I found a similar issue here: microsoft/TypeScript#5565

Deep Schema Test

The following validates as false, but should evaluate to true because the object being validated is the exact same as the schema created.

var schemaTester = schema({
    project_users: [{
        id: '5'
    }]
});

var valid = schemaTester({
    project_users: [{
        id: '5'
    }]
});

console.log(valid); // false but should be true

Typo in Array.js

Hi,

There's a typo in js-schema/lib/extensions/Array.js line 38

json.tpye = 'array'

should probably be

json.type = 'array'

Cheers,
Gary

Add "Predicate" extension

I would like to use a custom predicate if necessary to do things like

var Mystery = schema({
  even: Predicate(function (x) { return x % 2 === 0 })
});

sometimes getting `constructor.schema.unwrap is not a function` errors

I don't really know why this is happening, but I started getting

TypeError: constructor.schema.unwrap is not a function
    at Array.<anonymous> (/opt/node_modules/js-schema/lib/patterns/class.js:36:31)
    at lastDefinedResult (/opt/node_modules/js-schema/lib/BaseSchema.js:107:26)
    at Array.<anonymous> (/opt/node_modules/js-schema/lib/patterns/object.js:216:27)
    at lastDefinedResult (/opt/node_modules/js-schema/lib/BaseSchema.js:107:26)
    at Array.<anonymous> (/opt/node_modules/js-schema/lib/patterns/object.js:216:27)
    at lastDefinedResult (/opt/node_modules/js-schema/lib/BaseSchema.js:107:26)
    at Object.resolve (/opt/node_modules/js-schema/lib/extensions/Schema.js:10:31)
    at Object.ref.resolve (/opt/node_modules/js-schema/lib/extensions/Schema.js:33:46)
    at module.exports (/opt/node_modules/js-schema/lib/schema.js:22:17)
    ...

when trying to do this in a module:

exports.postSubmit = co(function*(req, res, next) {
  try {
    let validate = schema({
      nr: /[0-9]{7}|abc\-[a-z\-]+/i,
      index: Number,
      lNr: Number,
      value: [Number, String, Boolean],
      '*': null
    });
  } catch (err) {}
});

When moving the code outside of the coroutine, I don't get this error ..!?
This worked before - even inside a coroutine - but doesn't right now.

Strict mode

How do we force strict mode on the schemas ? Meaning that unspecified keys are unauthorized ? Thanks a lot

bower install fails because bower.json contains invalid JSON

Here is the invalid bit:

 "contributors": {
    "Alan James"
  },

This causes an error like this:

$ bower install js-schema
bower not-cached    git://github.com/molnarg/js-schema.git#*
bower resolve       git://github.com/molnarg/js-schema.git#*
bower download      https://github.com/molnarg/js-schema/archive/0.6.3.tar.gz
bower extract       js-schema#* archive.tar.gz
bower EMALFORMED    Failed to read /tmp/foo/bower/js-schema-28250-yvXDm3/bower.json

Additional error details:
Unexpected token }

You probably want to use this instead:

 "authors": [
    "Alan James"
  ],

Here is the relevant part of the bower.json spec:

authors

Type: Array of (String or Object)

A list of people that authored the contents of the package.

Either:

"authors": [
  "John Doe", "John Doe <[email protected]>",
  "John Doe <[email protected]> (http://johndoe.com)"
]

or:

"authors": [
  { "name": "John Doe" },
  { "name": "John Doe", "email": "[email protected]" },
  { "name": "John Doe", "email": "[email protected]"," homepage": "http://johndoe.com" }
]

Bower issue

when i try to install js-schema using a version number i get this error

bower install
...
...
bower js-schema#0.6.3                         ENORESTARGET No tag found that was able to satisfy 0.6.3

Additional error details:
No versions found in git://github.com/molnarg/js-schema.git

Bower Support

Because js-schema also supports frontend JavaScript it should be submitted to bower, which is the most popular frontend package manager.

It would be great to have it on NPM and Bower.

Strange error message when using .errors()

Here's the code:

var Book = schema({
  title: String
})
Book.errors({ title: 9 })
//=> { '0': '[object Object] doesn\'t match [object Object] check' }

I would expect the error message to be something similar to this:

var MyStr = schema(String)
MyStr.errors(9)
//=> '9 is not a String'

heterogeneous array

Is there a way to specify an array with items having different types? E.g. for data

['square' 1 1 4 4]
['circle' 1 1 3]

the schema should match either array of string 'square' + four numbers, or array of string 'circle' + three numbers. It should not match array of 'circle' + four numbers, or 'square' with three.

I haven't found a recursive Array declaration, only Array (match class only), Array.of (match homogeneous elements), and Array.like (exact match).

Feature request: Exact Array

First of all thanks for this awesome library!

This can be also a documentation issue because I could not figure out how to do it. I'd like to have a way to validate that I have some exact array in my object.

For example

{
  "ducks": [
    {"age": 1},
    {"age": 7}
  ]
}

Closest thing I managed to create is this:

var YoungDuck = schema({ age: 1 });
var OldDuck = schema({ age: 7 });

var Animals = schema({
  ducks: schema({
    0: YoungDuck,
    1: OldDuck
  }),
})

but this of course validates this too

{
  "ducks": [
    {"age": 1},
    {"age": 7},
    {"age": "bad!"}
  ]
}

Could it be possible to have something like this?

var Animals = schema({
  ducks: Array.exact([
    YoungDuck,
    OldDuck
  ]),
})

Testing Object w/ Properties

I may be just missing something in the docs here, but I really am stuck on this one, hoping someone can help me out.

I've got an object like this I want to validate:

var foo = {
  bar: { baz: 12, quux: 'wow' }
}

I want to validate it such that foo can optionally have a bar property or not, but if it does, bar is an object (not anything that is an instance of an object, which includes arrays), and that it has the keys baz and quux. Is there any way to do this with js-schema?

For the key, I assume I want ?bar, but I'm not sure what to put in for the value at all.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.