Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

jsen

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jsen - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

schema.md

97

lib/jsen.js

@@ -611,2 +611,83 @@ 'use strict';

function clone(obj) {
var cloned = obj,
objType = type(obj),
key, i;
if (objType === 'object') {
cloned = {};
for (key in obj) {
cloned[key] = clone(obj[key]);
}
}
else if (objType === 'array') {
cloned = [];
for (i = 0; i < obj.length; i++) {
cloned[i] = clone(obj[i]);
}
}
else if (objType === 'regexp') {
return new RegExp(obj);
}
else if (objType === 'date') {
return new Date(obj);
}
return cloned;
}
function build(schema, def, additional, resolver) {
var defType, defValue, key, i;
if (type(schema) !== 'object') {
return def;
}
schema = resolver.resolve(schema);
if (def === undefined && schema.hasOwnProperty('default')) {
def = clone(schema.default);
}
defType = type(def);
if (defType === 'object' && type(schema.properties) === 'object') {
for (key in schema.properties) {
defValue = build(schema.properties[key], def[key], additional, resolver);
if (defValue !== undefined) {
def[key] = defValue;
}
}
for (key in def) {
if (!(key in schema.properties) &&
(schema.additionalProperties === false ||
(additional === false && !schema.additionalProperties))) {
delete def[key];
}
}
}
else if (defType === 'array' && schema.items) {
if (type(schema.items) === 'array') {
for (i = 0; i < schema.items.length; i++) {
defValue = build(schema.items[i], def[i], additional, resolver);
if (defValue !== undefined || i < def.length) {
def[i] = defValue;
}
}
}
else if (def.length) {
for (i = 0; i < def.length; i++) {
def[i] = build(schema.items, def[i], additional, resolver);
}
}
}
return def;
}
function jsen(schema, options) {

@@ -685,2 +766,8 @@ if (type(schema) !== 'object') {

if (!message) {
message = key && schema.properties && schema.properties[key] && schema.properties[key].messages &&
schema.properties[key].messages[keyword] ||
schema.messages && schema.messages[keyword];
}
if (path.indexOf('[') > -1) {

@@ -817,2 +904,10 @@ // create error objects dynamically when path contains indexed property expressions

compiled.build = function (initial, options) {
return build(
schema,
(options && options.copy === false ? initial : clone(initial)),
options && options.additionalProperties,
resolver);
};
return compiled;

@@ -824,2 +919,4 @@ }

jsen.clone = clone;
module.exports = jsen;

2

package.json
{
"name": "jsen",
"version": "0.2.0",
"version": "0.3.0",
"description": "JSON-Schema validator built for speed.",

@@ -5,0 +5,0 @@ "author": "Veli Pehlivanov <bugventure@gmail.com>",

@@ -15,18 +15,4 @@ JSEN

- [Getting Started](#getting-started)
- [Performance & Benchmarks](#performance--benchmarks)
- [JSON Schema](#json-schema)
- [Type Validation](#type-validation)
- [`string`](#string)
- [`number`](#number)
- [`integer`](#integer)
- [`boolean`](#boolean)
- [`object`](#object)
- [`array`](#array)
- [`null`](#null)
- [`any`](#any)
- [Multi Schema Validation & Negation](#multi-schema-validation--negation)
- [`allOf`](#allof)
- [`anyOf`](#anyof)
- [`oneOf`](#oneof)
- [`not`](#not)
- [Schema Reference Using `$ref`](#schema-reference-using-ref)
- [Format Validation](#format-validation)

@@ -36,5 +22,10 @@ - [Custom Formats](#custom-formats)

- [Custom Errors](#custom-errors)
- [Custom Errors for Keywords](#custom-errors-for-keywords)
- [Gathering Default Values](#gathering-default-values)
- [options.copy](#optionscopy)
- [options.additionalProperties](#optionsadditionalproperties)
- [Tests](#tests)
- [Issues](#issues)
- [Changelog](#changelog)
- [v0.3.0](#v030)
- [v0.2.0](#v020)

@@ -101,261 +92,22 @@ - [v0.1.2](#v012)

## JSON Schema
## Performance & Benchmarks
`jsen` fully implements draft 4 of the [JSON Schema specification](http://json-schema.org/documentation.html). Check out this [excellent guide to JSON Schema](http://spacetelescope.github.io/understanding-json-schema/UnderstandingJSONSchema.pdf) by Michael Droettboom, et al.
JSEN uses dynamic code generation to produce a validator function that the V8 engine can optimize for performance. Following is a set of benchmarks where JSEN is compared to other JSON Schema validators for node.
A schema is a JavaScript object that specifies the type and structure of another JavaScript object or value. Here are some valid schema objects:
* [json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark)
* [z-schema bencrhmark](https://rawgit.com/zaggino/z-schema/master/benchmark/results.html)
* [jsck benchmark](https://github.com/pandastrike/jsck)
* [themis benchmark](https://github.com/playlyfe/themis)
* [cosmicrealms.com benchmark](https://github.com/Sembiance/cosmicrealms.com)
Schema | Matches
------ | -------
`{}` | any value
`{ type: 'string' }` | a JavaScript string
`{ type: 'number' } ` | a JavaScript number
`{ type: ['string', 'null'] }` | either a string or `null`
`{ type: 'object' }` | a JavaScript object
`{ type: 'array', items: { type: 'string' } }` | an array containing strings
More on V8 optimization: [Performance Tips for JavaScript in V8](http://www.html5rocks.com/en/tutorials/speed/v8/)
## Type Validation
## JSON Schema
### `string`
To get started with JSON Schema, check out the [JSEN schema guide](schema.md).
```javascript
{
type: 'string', // match a string
minLength: 3, // with minimum length 3 characters
maxLength: 10, // with maximum length 10 character
pattern: '^\\w$' // matching the regex /^\w$/
}
```
For further reading, check out this [excellent guide to JSON Schema](http://spacetelescope.github.io/understanding-json-schema/UnderstandingJSONSchema.pdf) by Michael Droettboom, et al.
JSEN fully implements draft 4 of the [JSON Schema specification](http://json-schema.org/documentation.html).
### `number`
```javascript
{
type: 'number', // match a number
minimum: 0, // with minimum value 0
maximum: 10, // with maximum value 10
exclusiveMinimum: true, // exclude the min value (default: false)
exclusiveMaximum: true, // exclude the max value (default: false)
multipleOf: 2 // the number must be a multiple of 2
}
```
### `integer`
Same as `number`, but matches integers only.
```javascript
{
type: 'integer', // match an integer number
minimum: 0, // with minimum value 0
maximum: 10, // with maximum value 10
exclusiveMinimum: true, // exclude the min value (default: false)
exclusiveMaximum: true, // exclude the max value (default: false)
multipleOf: 2 // the number must be a multiple of 2
}
```
### `boolean`
```javascript
{
type: 'boolean' // match a Boolean value
}
```
### `object`
```javascript
{
type: 'object', // match a JavaScript object
minProperties: 2, // having at least 2 properties
maxProperties: 5, // and at most 5 properties
required: ['id', 'name'], // where `id` and `name` are required
properties: { // and the properties are as follows
id: { type: 'string' },
name: { type: 'string' },
price: {
type: 'number',
mininum: 0
},
available: { type: 'boolean' }
},
patternProperties: { // with additional properties, where
'^unit-\w+$': { // the keys match the given regular
type: 'number', // expression and the values are
minimum: 0 // numbers with minimum value of 0
}
},
additionalProperties: false // do not allow any other properties
} // (default: true)
```
Alternatively `additionalProperties` can be an object defining a schema, where each additional property must conform to the specified schema.
```javascript
{
type: 'object', // match a JavaScript object
additionalProperties: { // with all properties containing
type: 'string' // string values
}
}
```
You can additionally specify `dependencies` in an object schema. There are two types of dependencies:
1. property dependency
```javascript
{
type: 'object', // if `price` is defined, then
dependencies: { // these two must also be defined
price: ['unitsInStock', 'quantityPerUnit']
}
}
```
2. schema dependency
``` javascript
{
type: 'object',
dependencies: { // if `price` is defined,
price: { // then the object must also
type: 'object', // match the specified schema
properties: {
unitsInStock: {
type: 'integer',
minimum: 0
}
}
}
}
}
```
### `array`
```javascript
{
type: 'array', // match a JavaScript array
minItems: 1, // with minimum 1 item
maxItems: 5, // and maximum 5 items
uniqueItems: true, // where items are unique
items: { // and each item is a number
type: 'number'
}
}
```
Alternatively, you can specify multiple item schemas for positional matching.
```javascript
{
type: 'array', // match a JavaScript array
items: [ // containing exactly 3 items
{ type: 'string' }, // where first item is a string
{ type: 'number' }, // and second item is a number
{ type: 'boolean' } // and third item is a Boolean value
]
}
```
### `null`
```javascript
{
type: 'null' // match a null value
}
```
### `any`
```javascript
{
type: 'any' // equivalent to `{}` (matches any value)
}
```
## Multi Schema Validation & Negation
### `allOf`
```javascript
{
allOf: [ // match a number conforming to both schemas,
{ // i.e. a numeric value between 3 and 5
type: 'number',
minimum: 0,
maximum: 5
},
{
type: 'number',
minimum: 3,
maximum: 10
}
]
}
```
### `anyOf`
```javascript
{
anyOf: [ // match either a string or a number
{ type: 'string' },
{ type: 'number' }
]
}
```
### `oneOf`
```javascript
{
oneOf: [ // match exacly one of those schemas,
{ // i.e. a number that is less than 3
type: 'number', // or greater than 5,
maximum: 52 // but not between 3 and 5
},
{
type: 'number',
minimum: 3
}
]
}
```
### `not`
```javascript
{
not: { // match a value that is not a JavaScript object
type: 'object'
}
}
```
## Schema Reference Using `$ref`
You can refer to types defined in other parts of the schema using the `$ref` property. This approach is often combined with the `definitions` section in the schema that contains reusable schema definitions.
```javascript
{
type: 'array', // match an array containing
items: { // items that are positive
$ref: '#/definitions/positiveInteger' // integers
},
definitions: {
positiveInteger: {
type: 'integer',
minimum: 0,
exclusiveMinimum: true
}
}
}
```
Using references, it becomes possible to validate complex object graphs using recursive schema definitions. For example, the validator itself validates the user schema against the [JSON meta-schema][metaschema].
## Format Validation

@@ -538,2 +290,130 @@

### Custom Errors for Keywords
You can assign custom error messages to keywords through the `messages` object in the JSON schema.
```javascript
var schema = {
type: 'object',
messages: {
type: 'Invalid data type where an object is expected'
}
}
var validate = jsen(schema);
validate('this is a string, not an object');
console.log(validate.errors);
/* Output:
[ { path: '',
keyword: 'type',
message: 'Invalid data type where an object is expected' } ]
*/
```
**NOTE**: The following keywords are never assigned to error objects, and thus do not support custom error messages: `items`, `properties`, `patternProperties`, `dependecies` (when defining a [schema dependency](http://json-schema.org/latest/json-schema-validation.html#anchor70)) and `allOf`.
## Gathering Default Values
JSEN can collect default values from the schema. The `build(initial, options)` method in the dynamic validator function recursively walks the schema object and compiles the default values into a single object or array.
```javascript
var validate = jsen({ type: 'string', default: 'abc' });
console.log(validate.build()); // 'abc'
var validate = jsen({
default: {},
properties: {
foo: { default: 'bar' },
arr: {
default: [],
items: [
{ default: 1 },
{ default: 1 },
{ default: 2 }
]
}
}
});
console.log(validate.build()); // { foo: 'bar', arr: [1, 2, 3] }
```
The `build` function can additionally merge the default values with an initially provided data object.
```javascript
var validate = jsen({
properties: {
rememberMe: {
default: 'true'
}
}
});
var initial = { username: 'John', password: 'P@$$w0rd' };
initial = validate.build(initial);
console.log(initial);
// { username: 'John', password: 'P@$$w0rd', rememberMe: true }
```
### options.copy
By default, the `build` function creates a copy of the initial data object. You can opt to modify the object in-place by passing `{ copy: false }` as a second argument.
```javascript
var initial = { username: 'John', password: 'P@$$w0rd' };
var validate = jsen({
properties: {
rememberMe: {
default: 'true'
}
}
});
var withDefaults = validate.build(initial);
console.log(withDefaults === initial); // false (initial is cloned)
withDefaults = validate.build(initial, { copy: false });
console.log(withDefaults === initial); // true (initial is modified)
```
### options.additionalProperties
The JSON schema spec allows additional properties by default. In many cases, however, this default behavior may be undesirable, forcing developers to specify `additionalProperties: false` everywhere in their schema objects. JSEN's `build` function can filter out additional properties by specifying `{ additionalProperties: false }` as a second argument.
```javascript
var validate = jsen({
properties: {
foo: {},
bar: {}
}
});
var initial = { foo: 1, bar: 2, baz: 3};
initial = validate.build(initial, { additionalProperties: false });
console.log(initial); // { foo: 1, bar: 2 }
```
When both `options.additionalProperties` and `schema.additionalProperties` are specified, the latter takes precedence.
```javascript
var validate = jsen({
additionalProperties: true,
properties: {
foo: {},
bar: {}
}
});
var initial = { foo: 1, bar: 2, baz: 3};
initial = validate.build(initial, { additionalProperties: false });
console.log(initial); // { foo: 1, bar: 2, baz: 3 }
```
NOTE: When `{ additionalProperties: false, copy: false }` is specified in the `build` options, any additional properties will be deleted from the initial data object.
## Tests

@@ -561,2 +441,7 @@

### v0.3.0
* Add support for default value population (#10)
* Add support for custom messages per keyword (#18)
### v0.2.0

@@ -610,4 +495,3 @@

[coveralls-url]: https://coveralls.io/r/bugventure/jsen
[metaschema]: http://json-schema.org/schema
[istanbul]: https://www.npmjs.org/package/istanbul
[mocha]: http://mochajs.org/

@@ -475,2 +475,342 @@ 'use strict';

});
describe('custom keyword messages', function () {
it('uses custom messages on keywords', function () {
var schemas = [
{
type: 'string',
messages: { type: 'custom message for keyword "type"' }
},
{
enum: [1, 2, 3],
messages: { enum: 'custom message for keyword "enum"' }
},
{
minimum: 3,
messages: { minimum: 'custom message for keyword "minimum"' }
},
{
minimum: 3,
exclusiveMinimum: true,
messages: { exclusiveMinimum: 'custom message for keyword "exclusiveMinimum"' }
},
{
maximum: 10,
messages: { maximum: 'custom message for keyword "maximum"' }
},
{
maximum: 10,
exclusiveMaximum: true,
messages: { exclusiveMaximum: 'custom message for keyword "exclusiveMaximum"' }
},
{
multipleOf: 5,
messages: { multipleOf: 'custom message for keyword "multipleOf"' }
},
{
minLength: 3,
messages: { minLength: 'custom message for keyword "minLength"' }
},
{
maxLength: 5,
messages: { maxLength: 'custom message for keyword "maxLength"' }
},
{
pattern: '\\d+',
messages: { pattern: 'custom message for keyword "pattern"' }
},
{
format: 'email',
messages: { format: 'custom message for keyword "format"' }
},
{
minItems: 1,
messages: { minItems: 'custom message for keyword "minItems"' }
},
{
maxItems: 1,
messages: { maxItems: 'custom message for keyword "maxItems"' }
},
{
additionalItems: false,
items: [{ type: 'string' }],
messages: { additionalItems: 'custom message for keyword "additionalItems"' }
},
{
uniqueItems: true,
messages: { uniqueItems: 'custom message for keyword "uniqueItems"' }
},
{
minProperties: 1,
messages: { minProperties: 'custom message for keyword "minProperties"' }
},
{
maxProperties: 1,
messages: { maxProperties: 'custom message for keyword "maxProperties"' }
},
{
required: ['foo'],
messages: { required: 'custom message for keyword "required"' }
},
{
required: ['foo'],
properties: {
foo: {
messages: {
required: 'custom message for keyword "required"'
}
}
}
},
{
required: ['foo'],
properties: {
foo: {
messages: {
required: 'this custom message for keyword "required" is assigned'
}
}
},
messages: { required: 'this custom message for keyword "required" is NOT assigned' }
},
{
additionalProperties: false,
messages: { additionalProperties: 'custom message for keyword "additionalProperties"' }
},
{
dependencies: {
foo: ['bar']
},
messages: { dependencies: 'custom message for keyword "dependencies"' }
},
{
anyOf: [
{ type: 'string' },
{ type: 'integer' }
],
messages: { anyOf: 'custom message for keyword "anyOf"' }
},
{
oneOf: [
{ type: 'string' },
{ type: 'integer' }
],
messages: { oneOf: 'custom message for keyword "oneOf"' }
},
{
not: {
type: 'string'
},
messages: { not: 'custom message for keyword "not"' }
}
],
data = [
123,
5,
1,
3,
11,
10,
12,
'ab',
'abcdef',
'abc',
'invalid email',
[],
[1, 2, 3],
['abc', 'def'],
[1, 2, 2],
{},
{ foo: 1, bar: 2 },
{},
{},
{},
{ foo: 'bar' },
{ foo: 'abc' },
null,
null,
'abc'
],
expectedMessages = [
schemas[0].messages.type,
schemas[1].messages.enum,
schemas[2].messages.minimum,
schemas[3].messages.exclusiveMinimum,
schemas[4].messages.maximum,
schemas[5].messages.exclusiveMaximum,
schemas[6].messages.multipleOf,
schemas[7].messages.minLength,
schemas[8].messages.maxLength,
schemas[9].messages.pattern,
schemas[10].messages.format,
schemas[11].messages.minItems,
schemas[12].messages.maxItems,
schemas[13].messages.additionalItems,
schemas[14].messages.uniqueItems,
schemas[15].messages.minProperties,
schemas[16].messages.maxProperties,
schemas[17].messages.required,
schemas[18].properties.foo.messages.required,
schemas[19].properties.foo.messages.required,
schemas[20].messages.additionalProperties,
schemas[21].messages.dependencies,
schemas[22].messages.anyOf,
schemas[23].messages.oneOf,
schemas[24].messages.not
],
validate,
valid;
schemas.forEach(function (schema, index) {
validate = jsen(schema);
valid = validate(data[index]);
assert(!valid);
assert.strictEqual(validate.errors[validate.errors.length - 1].message, expectedMessages[index]);
});
});
it('does not use custom messages on keyword: items (object)', function () {
var schema = {
items: {
type: 'string',
messages: {
type: 'will be assigned'
}
},
messages: {
items: 'will not be assigned'
}
},
validate = jsen(schema),
valid = validate([123]);
assert(!valid);
assert.strictEqual(validate.errors.length, 1);
assert.strictEqual(validate.errors[0].message, 'will be assigned');
});
it('does not use custom messages on keyword: items (array)', function () {
var schema = {
items: [{
type: 'string',
messages: {
type: 'will be assigned'
}
}],
messages: {
items: 'will not be assigned'
}
},
validate = jsen(schema),
valid = validate([123, 123]);
assert(!valid);
assert.strictEqual(validate.errors.length, 1);
assert.strictEqual(validate.errors[0].message, 'will be assigned');
});
it('does not use custom messages on keyword: properties', function () {
var schema = {
properties: {
foo: {
type: 'number',
messages: {
type: 'will be assigned'
}
}
},
messages: {
properties: 'will not be assigned'
}
},
validate = jsen(schema),
valid = validate({ foo: 'bar' });
assert(!valid);
assert.strictEqual(validate.errors.length, 1);
assert.strictEqual(validate.errors[0].message, 'will be assigned');
});
it('does not use custom messages on keyword: patternProperties', function () {
var schema = {
patternProperties: {
'^foo$': {
type: 'number',
messages: {
type: 'will be assigned'
}
}
},
messages: {
patternProperties: 'will not be assigned'
}
},
validate = jsen(schema),
valid = validate({ foo: 'bar' });
assert(!valid);
assert.strictEqual(validate.errors.length, 1);
assert.strictEqual(validate.errors[0].message, 'will be assigned');
});
it('does not use custom messages on keyword: dependencies (schema)', function () {
var schema = {
dependencies: {
foo: {
minProperties: 2,
messages: {
minProperties: 'will be assigned'
}
}
},
messages: {
dependencies: 'will not be assigned'
}
},
validate = jsen(schema),
valid = validate({ foo: 'bar' });
assert(!valid);
assert.strictEqual(validate.errors.length, 1);
assert.strictEqual(validate.errors[0].message, 'will be assigned');
});
it('does not use custom messages on keyword: allOf', function () {
var schema = {
dependencies: {
foo: {
minProperties: 2,
messages: {
minProperties: 'will be assigned'
}
}
},
allOf: [
{
minimum: 2,
messages: {
minimum: 'will not be assigned'
}
},
{
maximum: 5,
messages: {
maximum: 'will be assigned'
}
}
],
messages: {
allOf: 'will not be assigned'
}
},
validate = jsen(schema),
valid = validate(6);
assert(!valid);
assert.strictEqual(validate.errors.length, 1);
assert.strictEqual(validate.errors[0].message, 'will be assigned');
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc