is-my-json-valid
Advanced tools
Comparing version 1.4.1 to 2.0.0
525
index.js
@@ -1,17 +0,522 @@ | ||
var orderly = require('orderly') | ||
var compile = require('./compile') | ||
var genobj = require('generate-object-property') | ||
var genfun = require('generate-function') | ||
var jsonpointer = require('jsonpointer') | ||
var formats = require('./formats') | ||
module.exports = function(schema) { | ||
if (Buffer.isBuffer(schema)) schema = schema.toString() | ||
var a = function(type) { | ||
switch (type) { | ||
case 'array': | ||
case 'object': | ||
case 'integer': | ||
return 'an '+type | ||
if (typeof schema === 'string') { | ||
try { | ||
schema = orderly.parse(schema) | ||
} catch (err) { | ||
if (schema.trim()[0] !== '{') throw err | ||
schema = JSON.parse(schema) | ||
default: | ||
return 'a '+type | ||
} | ||
} | ||
var get = function(obj, ptr) { | ||
if (/^https?:\/\//.test(ptr)) return null | ||
ptr = ptr.replace(/^#/, '') | ||
ptr = ptr.replace(/\/$/, '') | ||
try { | ||
return jsonpointer.get(obj, decodeURI(ptr)) | ||
} catch (err) { | ||
return null | ||
} | ||
} | ||
var formatName = function(field) { | ||
return field.replace(/\[[^\]]+\]/g, '.*') | ||
} | ||
var types = {} | ||
types.any = function() { | ||
return 'true' | ||
} | ||
types.null = function(name) { | ||
return name+' === null' | ||
} | ||
types.boolean = function(name) { | ||
return 'typeof '+name+' === "boolean"' | ||
} | ||
types.array = function(name) { | ||
return 'Array.isArray('+name+')' | ||
} | ||
types.object = function(name) { | ||
return 'typeof '+name+' === "object" && '+name+' && !Array.isArray('+name+')' | ||
} | ||
types.number = function(name) { | ||
return 'typeof '+name+' === "number"' | ||
} | ||
types.integer = function(name) { | ||
return 'typeof '+name+' === "number" && ('+name+' | 0) === '+name | ||
} | ||
types.string = function(name) { | ||
return 'typeof '+name+' === "string"' | ||
} | ||
var unique = function(array) { | ||
var list = [] | ||
for (var i = 0; i < array.length; i++) { | ||
list.push(typeof array[i] === 'object' ? JSON.stringify(array[i]) : array[i]) | ||
} | ||
for (var i = 1; i < list.length; i++) { | ||
if (list.indexOf(list[i]) !== i) return false | ||
} | ||
return true | ||
} | ||
var toType = function(node) { | ||
return node.type | ||
} | ||
var compile = function(schema, cache, root) { | ||
if (!cache) cache = {} | ||
if (!root) root = schema | ||
var scope = {unique:unique, formats:formats} | ||
var syms = {} | ||
var gensym = function(name) { | ||
return name+(syms[name] = (syms[name] || 0)+1) | ||
} | ||
var reversePatterns = {} | ||
var patterns = function(p) { | ||
if (reversePatterns[p]) return reversePatterns[p] | ||
var n = gensym('pattern') | ||
scope[n] = new RegExp(p) | ||
reversePatterns[p] = n | ||
return n | ||
} | ||
var vars = ['i','j','k','l','m','n','o','p','q','r','s','t','u','v','x','y','z'] | ||
var genloop = function() { | ||
var v = vars.shift() | ||
vars.push(v+v[0]) | ||
return v | ||
} | ||
var visit = function(name, node) { | ||
if (Array.isArray(node.items)) { // tuple type | ||
node.properties = {} | ||
node.items.forEach(function(item, i) { | ||
node.properties[i] = item | ||
}) | ||
node.type = 'array' | ||
node.tuple = true | ||
} | ||
var indent = 0 | ||
var error = function(msg) { | ||
var n = gensym('error') | ||
scope[n] = {field:formatName(name), message:msg} | ||
validate | ||
('errors++') | ||
('if (validate.errors === null) validate.errors = []') | ||
('validate.errors.push(%s)', n) | ||
} | ||
if (node.required === true) { | ||
indent++ | ||
validate('if (%s === undefined) {', name) | ||
error('is required') | ||
validate('} else {') | ||
} else if (node.required) { | ||
indent++ | ||
var isUndefined = function(req) { | ||
return genobj(name, req) + ' === undefined' | ||
} | ||
validate('if (%s) {', node.required.map(isUndefined).join(' || ') || 'false') | ||
error('missing required properties') | ||
validate('} else {') | ||
} else { | ||
indent++ | ||
validate('if (%s !== undefined) {', name) | ||
} | ||
var valid = [].concat(node.type) | ||
.map(function(t) { | ||
return types[t || 'any'](name) | ||
}) | ||
.join(' || ') || 'true' | ||
if (valid !== 'true') { | ||
indent++ | ||
validate('if (!(%s)) {', valid) | ||
error('is the wrong type') | ||
validate('} else {') | ||
} | ||
if (node.tuple) { | ||
if (node.additionalItems === false) { | ||
validate('if (%s.length > %d) {', name, node.items.length) | ||
error('has additional items') | ||
validate('}') | ||
} else if (node.additionalItems) { | ||
var i = genloop() | ||
validate('for (var %s = %d; %s < %s.length; %s++) {', i, node.items.length, i, name, i) | ||
visit(name+'['+i+']', node.additionalItems) | ||
validate('}') | ||
} | ||
} | ||
if (node.format && formats[node.format]) { | ||
var n = gensym('format') | ||
scope[n] = formats[node.format] | ||
validate('if (!%s.test(%s)) {', n, name) | ||
error('must be '+node.format+' format') | ||
validate('}') | ||
} | ||
if (node.uniqueItems) { | ||
if (node.type !== 'array') validate('if (%s) {', types.array(name)) | ||
validate('if (!(unique(%s))) {', name) | ||
error('must be unique') | ||
validate('}') | ||
if (node.type !== 'array') validate('}') | ||
} | ||
if (node.properties) { | ||
Object.keys(node.properties).forEach(function(p) { | ||
visit(genobj(name, p), node.properties[p]) | ||
var def = node.properties[p].default | ||
if (def !== undefined) validate('if (%s === undefined) %s = %s', genobj(name, p), genobj(name, p), JSON.stringify(def)) | ||
}) | ||
} | ||
if (node.enum) { | ||
var complex = node.enum.some(function(e) { | ||
return typeof e === 'object' | ||
}) | ||
var compare = complex ? | ||
function(e) { | ||
return 'JSON.stringify('+name+')'+' !== JSON.stringify('+JSON.stringify(e)+')' | ||
} : | ||
function(e) { | ||
return name+' !== '+JSON.stringify(e) | ||
} | ||
validate('if (%s) {', node.enum.map(compare).join(' && ') || 'false') | ||
error('must be an enum value') | ||
validate('}') | ||
} | ||
if (node.dependencies) { | ||
if (node.type !== 'object') validate('if (%s) {', types.object(name)) | ||
Object.keys(node.dependencies).forEach(function(key) { | ||
var deps = node.dependencies[key] | ||
if (typeof deps === 'string') deps = [deps] | ||
var exists = function(k) { | ||
return genobj(name, k) + ' !== undefined' | ||
} | ||
if (Array.isArray(deps)) { | ||
validate('if (%s !== undefined && !(%s)) {', genobj(name, key), deps.map(exists).join(' && ') || 'true') | ||
error('dependencies not set') | ||
validate('}') | ||
} | ||
if (typeof deps === 'object') { | ||
validate('if (%s !== undefined) {', genobj(name, key)) | ||
visit(name, deps) | ||
validate('}') | ||
} | ||
}) | ||
if (node.type !== 'object') validate('}') | ||
} | ||
if (node.additionalProperties || node.additionalProperties === false) { | ||
if (node.type !== 'object') validate('if (%s) {', types.object(name)) | ||
var i = genloop() | ||
var keys = gensym('keys') | ||
var toCompare = function(p) { | ||
return keys+'['+i+'] !== '+JSON.stringify(p) | ||
} | ||
var toTest = function(p) { | ||
return '!'+patterns(p)+'.test('+keys+'['+i+'])' | ||
} | ||
var additionalProp = Object.keys(node.properties || {}).map(toCompare) | ||
.concat(Object.keys(node.patternProperties || {}).map(toTest)) | ||
.join(' && ') || 'true' | ||
validate('var %s = Object.keys(%s)', keys, name) | ||
('for (var %s = 0; %s < %s.length; %s++) {', i, i, keys, i) | ||
('if (%s) {', additionalProp) | ||
if (node.additionalProperties === false) { | ||
error('has additional properties') | ||
} else { | ||
visit(name+'['+keys+'['+i+']]', node.additionalProperties) | ||
} | ||
validate | ||
('}') | ||
('}') | ||
if (node.type !== 'object') validate('}') | ||
} | ||
if (node.$ref) { | ||
var sub = get(root, node.$ref) | ||
if (sub) { | ||
var fn = cache[node.$ref] | ||
if (!fn) { | ||
cache[node.$ref] = function proxy(data) { | ||
var valid = fn(data) | ||
proxy.errors = fn.errors | ||
return valid | ||
} | ||
fn = compile(sub, cache, root) | ||
} | ||
var n = gensym('ref') | ||
scope[n] = fn | ||
validate | ||
('if (!(%s(%s))) {', n, name) | ||
('errors++') | ||
('if (validate.errors === null) validate.errors = []') | ||
('validate.errors.push.apply(validate.errors, %s.errors)', n) | ||
('}') | ||
} | ||
} | ||
if (node.not) { | ||
var prev = gensym('prev') | ||
validate('var %s = errors', prev) | ||
visit(name, node.not) | ||
validate('if (%s === errors) {', prev) | ||
error('negative schema matches') | ||
validate('} else {') | ||
('errors = %s', prev) | ||
('}') | ||
} | ||
if (node.items && !node.tuple) { | ||
if (node.type !== 'array') validate('if (%s) {', types.array(name)) | ||
var i = genloop() | ||
validate('for (var %s = 0; %s < %s.length; %s++) {', i, i, name, i) | ||
visit(name+'['+i+']', node.items) | ||
validate('}') | ||
if (node.type !== 'array') validate('}') | ||
} | ||
if (node.patternProperties) { | ||
if (node.type !== 'object') validate('if (%s) {', types.object(name)) | ||
var keys = gensym('keys') | ||
var i = genloop() | ||
validate | ||
('var %s = Object.keys(%s)', keys, name) | ||
('for (var %s = 0; %s < %s.length; %s++) {', i, i, keys, i) | ||
Object.keys(node.patternProperties).forEach(function(key) { | ||
var p = patterns(key) | ||
validate('if (%s.test(%s)) {', p, keys+'['+i+']') | ||
visit(name+'['+keys+'['+i+']]', node.patternProperties[key]) | ||
validate('}') | ||
}) | ||
validate('}') | ||
if (node.type !== 'object') validate('}') | ||
} | ||
if (node.pattern) { | ||
var p = patterns(node.pattern) | ||
if (node.type !== 'string') validate('if (%s) {', types.string(name)) | ||
validate('if (!(%s.test(%s))) {', p, name) | ||
error('pattern mismatch') | ||
validate('}') | ||
if (node.type !== 'string') validate('}') | ||
} | ||
if (node.allOf) { | ||
node.allOf.forEach(function(sch) { | ||
visit(name, sch) | ||
}) | ||
} | ||
if (node.anyOf && node.anyOf.length) { | ||
var prev = gensym('prev') | ||
node.anyOf.forEach(function(sch, i) { | ||
if (i === 0) { | ||
validate('var %s = errors', prev) | ||
} else { | ||
validate('if (errors !== %s) {', prev) | ||
('errors = %s', prev) | ||
} | ||
visit(name, sch) | ||
}) | ||
node.anyOf.forEach(function(sch, i) { | ||
if (i) validate('}') | ||
}) | ||
validate('if (%s !== errors) {', prev) | ||
error('no schemas match') | ||
validate('}') | ||
} | ||
if (node.oneOf && node.oneOf.length) { | ||
var prev = gensym('prev') | ||
var passes = gensym('passes') | ||
validate | ||
('var %s = errors', prev) | ||
('var %s = 0', passes) | ||
node.oneOf.forEach(function(sch, i) { | ||
visit(name, sch) | ||
validate('if (%s === errors) {', prev) | ||
('%s++', passes) | ||
('} else {') | ||
('errors = %s', prev) | ||
('}') | ||
}) | ||
validate('if (%s !== 1) {', passes) | ||
error('no (or more than one) schemas match') | ||
validate('}') | ||
} | ||
if (node.multipleOf !== undefined) { | ||
if (node.type !== 'number' && node.type !== 'integer') validate('if (%s) {', types.number(name)) | ||
var factor = ((node.multipleOf | 0) !== node.multipleOf) ? Math.pow(10, node.multipleOf.toString().split('.').pop().length) : 1 | ||
if (factor > 1) validate('if ((%d*%s) % %d) {', factor, name, factor*node.multipleOf) | ||
else validate('if (%s % %d) {', name, node.multipleOf) | ||
error('has a remainder') | ||
validate('}') | ||
if (node.type !== 'number' && node.type !== 'integer') validate('}') | ||
} | ||
if (node.maxProperties !== undefined) { | ||
if (node.type !== 'object') validate('if (%s) {', types.object(name)) | ||
validate('if (Object.keys(%s).length > %d) {', name, node.maxProperties) | ||
error('has more properties than allowed') | ||
validate('}') | ||
if (node.type !== 'object') validate('}') | ||
} | ||
if (node.minProperties !== undefined) { | ||
if (node.type !== 'object') validate('if (%s) {', types.object(name)) | ||
validate('if (Object.keys(%s).length < %d) {', name, node.minProperties) | ||
error('has less properties than allowed') | ||
validate('}') | ||
if (node.type !== 'object') validate('}') | ||
} | ||
if (node.maxItems !== undefined) { | ||
if (node.type !== 'array') validate('if (%s) {', types.array(name)) | ||
validate('if (%s.length > %d) {', name, node.maxItems) | ||
error('has more items than allowed') | ||
validate('}') | ||
if (node.type !== 'array') validate('}') | ||
} | ||
if (node.minItems !== undefined) { | ||
if (node.type !== 'array') validate('if (%s) {', types.array(name)) | ||
validate('if (%s.length < %d) {', name, node.minItems) | ||
error('has less items than allowed') | ||
validate('}') | ||
if (node.type !== 'array') validate('}') | ||
} | ||
if (node.maxLength !== undefined) { | ||
if (node.type !== 'string') validate('if (%s) {', types.string(name)) | ||
validate('if (%s.length > %d) {', name, node.maxLength) | ||
error('has longer length than allowed') | ||
validate('}') | ||
if (node.type !== 'string') validate('}') | ||
} | ||
if (node.minLength !== undefined) { | ||
if (node.type !== 'string') validate('if (%s) {', types.string(name)) | ||
validate('if (%s.length < %d) {', name, node.minLength) | ||
error('has less length than allowed') | ||
validate('}') | ||
if (node.type !== 'string') validate('}') | ||
} | ||
if (node.minimum !== undefined) { | ||
validate('if (%s %s %d) {', name, node.exclusiveMinimum ? '<=' : '<', node.minimum) | ||
error('is less than minimum') | ||
validate('}') | ||
} | ||
if (node.maximum !== undefined) { | ||
validate('if (%s %s %d) {', name, node.exclusiveMaximum ? '>=' : '>', node.maximum) | ||
error('is more than maximum') | ||
validate('}') | ||
} | ||
while (indent--) validate('}') | ||
} | ||
var validate = genfun | ||
('function validate(data) {') | ||
('validate.errors = null') | ||
('var errors = 0') | ||
visit('data', schema) | ||
validate | ||
('return errors === 0') | ||
('}') | ||
validate = validate.toFunction(scope) | ||
validate.errors = null | ||
validate.__defineGetter__('error', function() { | ||
if (!validate.errors) return '' | ||
return validate.errors | ||
.map(function(err) { | ||
return err.field+' '+err.message | ||
}) | ||
.join('\n') | ||
}) | ||
validate.toJSON = function() { | ||
return sch | ||
} | ||
return validate | ||
} | ||
module.exports = function(schema) { | ||
if (typeof schema === 'string') schema = JSON.parse(schema) | ||
return compile(schema) | ||
} |
{ | ||
"name": "is-my-json-valid", | ||
"version": "1.4.1", | ||
"description": "A JSONSchema / orderly validator that uses code generation to be extremely fast", | ||
"version": "2.0.0", | ||
"description": "A JSONSchema validator that uses code generation to be extremely fast", | ||
"main": "index.js", | ||
@@ -9,4 +9,3 @@ "dependencies": { | ||
"generate-object-property": "^1.1.0", | ||
"orderly": "^1.1.0", | ||
"xtend": "^4.0.0" | ||
"jsonpointer": "^1.1.0" | ||
}, | ||
@@ -13,0 +12,0 @@ "devDependencies": { |
# is-my-json-valid | ||
A [JSONSchema](http://json-schema.org/)/[orderly](http://orderly-json.org/) validator that uses code generation | ||
A [JSONSchema](http://json-schema.org/) validator that uses code generation | ||
to be extremely fast | ||
@@ -10,2 +10,4 @@ | ||
It supports passes the entire JSONSchema v4 test suite except for `remoteRefs` and `maxLength` and `minLength` with the use of unicode surrogate pairs. | ||
[![build status](http://img.shields.io/travis/mafintosh/is-my-json-valid.svg?style=flat)](http://travis-ci.org/mafintosh/is-my-json-valid) | ||
@@ -21,2 +23,3 @@ | ||
var validate = validator({ | ||
required: true, | ||
type: 'object', | ||
@@ -45,12 +48,2 @@ properties: { | ||
Or pass a orderly schema | ||
``` js | ||
var validate = validate( | ||
'object {'+ | ||
' string name;'+ | ||
'}*;' | ||
) | ||
``` | ||
Optionally you can use the require submodule to load a schema from `__dirname` | ||
@@ -57,0 +50,0 @@ |
var tape = require('tape') | ||
var fs = require('fs') | ||
var cosmic = require('./fixtures/cosmic') | ||
@@ -6,26 +7,5 @@ var validator = require('../') | ||
tape('orderly', function(t) { | ||
var validate = validatorRequire('./fixtures/test.schema') | ||
t.ok(validate({ | ||
name: 'test' | ||
}), 'should be valid') | ||
t.ok(validate({ | ||
name: 53 | ||
}), 'should be valid') | ||
t.notOk(validate({ | ||
name: false | ||
}), 'should be invalid') | ||
t.same(validate.errors, [{field:'data.name', message:'must be a string or a number'}]) | ||
t.notOk(validate(), 'should be invalid') | ||
t.end() | ||
}) | ||
tape('simple', function(t) { | ||
var schema = { | ||
required: true, | ||
type: 'object', | ||
@@ -155,1 +135,21 @@ properties: { | ||
}) | ||
var files = fs.readdirSync(__dirname+'/json-schema-draft4') | ||
.map(function(file) { | ||
if (file === 'definitions.json') return null | ||
if (file === 'refRemote.json') return null | ||
return require('./json-schema-draft4/'+file) | ||
}) | ||
.filter(Boolean) | ||
files.forEach(function(file) { | ||
file.forEach(function(f) { | ||
tape('json-schema-test-suite '+f.description, function(t) { | ||
var validate = validator(f.schema) | ||
f.tests.forEach(function(test) { | ||
t.same(validate(test.data), test.valid, test.description) | ||
}) | ||
t.end() | ||
}) | ||
}) | ||
}) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
83509
3
40
2694
82
4
1
+ Addedjsonpointer@^1.1.0
+ Addedjsonpointer@1.1.0(transitive)
- Removedorderly@^1.1.0
- Removedxtend@^4.0.0
- Removedansi-styles@1.0.0(transitive)
- Removedchalk@0.4.0(transitive)
- Removedhas-color@0.1.7(transitive)
- Removednomnom@1.8.1(transitive)
- Removedorderly@1.1.0(transitive)
- Removedstrip-ansi@0.1.1(transitive)
- Removedunderscore@1.6.0(transitive)
- Removedxtend@4.0.2(transitive)