UNPKG

json-editor

Version:
576 lines (531 loc) 17.6 kB
JSONEditor.Validator = Class.extend({ init: function(jsoneditor,schema,options) { this.jsoneditor = jsoneditor; this.schema = schema || this.jsoneditor.schema; this.options = options || {}; this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate; }, validate: function(value) { return this._validateSchema(this.schema, value); }, _validateSchema: function(schema,value,path) { var self = this; var errors = []; var valid, i, j; var stringified = JSON.stringify(value); path = path || 'root'; // Work on a copy of the schema schema = $extend({},this.jsoneditor.expandRefs(schema)); /* * Type Agnostic Validation */ // Version 3 `required` if(schema.required && schema.required === true) { if(typeof value === "undefined") { errors.push({ path: path, property: 'required', message: this.translate("error_notset") }); // Can't do any more validation at this point return errors; } } // Value not defined else if(typeof value === "undefined") { // If required_by_default is set, all fields are required if(this.jsoneditor.options.required_by_default) { errors.push({ path: path, property: 'required', message: this.translate("error_notset") }); } // Not required, no further validation needed else { return errors; } } // `enum` if(schema["enum"]) { valid = false; for(i=0; i<schema["enum"].length; i++) { if(stringified === JSON.stringify(schema["enum"][i])) valid = true; } if(!valid) { errors.push({ path: path, property: 'enum', message: this.translate("error_enum") }); } } // `extends` (version 3) if(schema["extends"]) { for(i=0; i<schema["extends"].length; i++) { errors = errors.concat(this._validateSchema(schema["extends"][i],value,path)); } } // `allOf` if(schema.allOf) { for(i=0; i<schema.allOf.length; i++) { errors = errors.concat(this._validateSchema(schema.allOf[i],value,path)); } } // `anyOf` if(schema.anyOf) { valid = false; for(i=0; i<schema.anyOf.length; i++) { if(!this._validateSchema(schema.anyOf[i],value,path).length) { valid = true; break; } } if(!valid) { errors.push({ path: path, property: 'anyOf', message: this.translate('error_anyOf') }); } } // `oneOf` if(schema.oneOf) { valid = 0; var oneof_errors = []; for(i=0; i<schema.oneOf.length; i++) { // Set the error paths to be path.oneOf[i].rest.of.path var tmp = this._validateSchema(schema.oneOf[i],value,path); if(!tmp.length) { valid++; } for(j=0; j<tmp.length; j++) { tmp[j].path = path+'.oneOf['+i+']'+tmp[j].path.substr(path.length); } oneof_errors = oneof_errors.concat(tmp); } if(valid !== 1) { errors.push({ path: path, property: 'oneOf', message: this.translate('error_oneOf', [valid]) }); errors = errors.concat(oneof_errors); } } // `not` if(schema.not) { if(!this._validateSchema(schema.not,value,path).length) { errors.push({ path: path, property: 'not', message: this.translate('error_not') }); } } // `type` (both Version 3 and Version 4 support) if(schema.type) { // Union type if(Array.isArray(schema.type)) { valid = false; for(i=0;i<schema.type.length;i++) { if(this._checkType(schema.type[i], value)) { valid = true; break; } } if(!valid) { errors.push({ path: path, property: 'type', message: this.translate('error_type_union') }); } } // Simple type else { if(!this._checkType(schema.type, value)) { errors.push({ path: path, property: 'type', message: this.translate('error_type', [schema.type]) }); } } } // `disallow` (version 3) if(schema.disallow) { // Union type if(Array.isArray(schema.disallow)) { valid = true; for(i=0;i<schema.disallow.length;i++) { if(this._checkType(schema.disallow[i], value)) { valid = false; break; } } if(!valid) { errors.push({ path: path, property: 'disallow', message: this.translate('error_disallow_union') }); } } // Simple type else { if(this._checkType(schema.disallow, value)) { errors.push({ path: path, property: 'disallow', message: this.translate('error_disallow', [schema.disallow]) }); } } } /* * Type Specific Validation */ // Number Specific Validation if(typeof value === "number") { // `multipleOf` and `divisibleBy` if(schema.multipleOf || schema.divisibleBy) { var divisor = schema.multipleOf || schema.divisibleBy; // Vanilla JS, prone to floating point rounding errors (e.g. 1.14 / .01 == 113.99999) valid = (value/divisor === Math.floor(value/divisor)); // Use math.js is available if(window.math) { valid = window.math.mod(window.math.bignumber(value), window.math.bignumber(divisor)).equals(0); } // Use decimal.js is available else if(window.Decimal) { valid = (new window.Decimal(value)).mod(new window.Decimal(divisor)).equals(0); } if(!valid) { errors.push({ path: path, property: schema.multipleOf? 'multipleOf' : 'divisibleBy', message: this.translate('error_multipleOf', [divisor]) }); } } // `maximum` if(schema.hasOwnProperty('maximum')) { // Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1) valid = schema.exclusiveMaximum? (value < schema.maximum) : (value <= schema.maximum); // Use math.js is available if(window.math) { valid = window.math[schema.exclusiveMaximum?'smaller':'smallerEq']( window.math.bignumber(value), window.math.bignumber(schema.maximum) ); } // Use Decimal.js if available else if(window.Decimal) { valid = (new window.Decimal(value))[schema.exclusiveMaximum?'lt':'lte'](new window.Decimal(schema.maximum)); } if(!valid) { errors.push({ path: path, property: 'maximum', message: this.translate( (schema.exclusiveMaximum?'error_maximum_excl':'error_maximum_incl'), [schema.maximum] ) }); } } // `minimum` if(schema.hasOwnProperty('minimum')) { // Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1) valid = schema.exclusiveMinimum? (value > schema.minimum) : (value >= schema.minimum); // Use math.js is available if(window.math) { valid = window.math[schema.exclusiveMinimum?'larger':'largerEq']( window.math.bignumber(value), window.math.bignumber(schema.minimum) ); } // Use Decimal.js if available else if(window.Decimal) { valid = (new window.Decimal(value))[schema.exclusiveMinimum?'gt':'gte'](new window.Decimal(schema.minimum)); } if(!valid) { errors.push({ path: path, property: 'minimum', message: this.translate( (schema.exclusiveMinimum?'error_minimum_excl':'error_minimum_incl'), [schema.minimum] ) }); } } } // String specific validation else if(typeof value === "string") { // `maxLength` if(schema.maxLength) { if((value+"").length > schema.maxLength) { errors.push({ path: path, property: 'maxLength', message: this.translate('error_maxLength', [schema.maxLength]) }); } } // `minLength` if(schema.minLength) { if((value+"").length < schema.minLength) { errors.push({ path: path, property: 'minLength', message: this.translate((schema.minLength===1?'error_notempty':'error_minLength'), [schema.minLength]) }); } } // `pattern` if(schema.pattern) { if(!(new RegExp(schema.pattern)).test(value)) { errors.push({ path: path, property: 'pattern', message: this.translate('error_pattern', [schema.pattern]) }); } } } // Array specific validation else if(typeof value === "object" && value !== null && Array.isArray(value)) { // `items` and `additionalItems` if(schema.items) { // `items` is an array if(Array.isArray(schema.items)) { for(i=0; i<value.length; i++) { // If this item has a specific schema tied to it // Validate against it if(schema.items[i]) { errors = errors.concat(this._validateSchema(schema.items[i],value[i],path+'.'+i)); } // If all additional items are allowed else if(schema.additionalItems === true) { break; } // If additional items is a schema // TODO: Incompatibility between version 3 and 4 of the spec else if(schema.additionalItems) { errors = errors.concat(this._validateSchema(schema.additionalItems,value[i],path+'.'+i)); } // If no additional items are allowed else if(schema.additionalItems === false) { errors.push({ path: path, property: 'additionalItems', message: this.translate('error_additionalItems') }); break; } // Default for `additionalItems` is an empty schema else { break; } } } // `items` is a schema else { // Each item in the array must validate against the schema for(i=0; i<value.length; i++) { errors = errors.concat(this._validateSchema(schema.items,value[i],path+'.'+i)); } } } // `maxItems` if(schema.maxItems) { if(value.length > schema.maxItems) { errors.push({ path: path, property: 'maxItems', message: this.translate('error_maxItems', [schema.maxItems]) }); } } // `minItems` if(schema.minItems) { if(value.length < schema.minItems) { errors.push({ path: path, property: 'minItems', message: this.translate('error_minItems', [schema.minItems]) }); } } // `uniqueItems` if(schema.uniqueItems) { var seen = {}; for(i=0; i<value.length; i++) { valid = JSON.stringify(value[i]); if(seen[valid]) { errors.push({ path: path, property: 'uniqueItems', message: this.translate('error_uniqueItems') }); break; } seen[valid] = true; } } } // Object specific validation else if(typeof value === "object" && value !== null) { // `maxProperties` if(schema.maxProperties) { valid = 0; for(i in value) { if(!value.hasOwnProperty(i)) continue; valid++; } if(valid > schema.maxProperties) { errors.push({ path: path, property: 'maxProperties', message: this.translate('error_maxProperties', [schema.maxProperties]) }); } } // `minProperties` if(schema.minProperties) { valid = 0; for(i in value) { if(!value.hasOwnProperty(i)) continue; valid++; } if(valid < schema.minProperties) { errors.push({ path: path, property: 'minProperties', message: this.translate('error_minProperties', [schema.minProperties]) }); } } // Version 4 `required` if(schema.required && Array.isArray(schema.required)) { for(i=0; i<schema.required.length; i++) { if(typeof value[schema.required[i]] === "undefined") { errors.push({ path: path, property: 'required', message: this.translate('error_required', [schema.required[i]]) }); } } } // `properties` var validated_properties = {}; if(schema.properties) { for(i in schema.properties) { if(!schema.properties.hasOwnProperty(i)) continue; validated_properties[i] = true; errors = errors.concat(this._validateSchema(schema.properties[i],value[i],path+'.'+i)); } } // `patternProperties` if(schema.patternProperties) { for(i in schema.patternProperties) { if(!schema.patternProperties.hasOwnProperty(i)) continue; var regex = new RegExp(i); // Check which properties match for(j in value) { if(!value.hasOwnProperty(j)) continue; if(regex.test(j)) { validated_properties[j] = true; errors = errors.concat(this._validateSchema(schema.patternProperties[i],value[j],path+'.'+j)); } } } } // The no_additional_properties option currently doesn't work with extended schemas that use oneOf or anyOf if(typeof schema.additionalProperties === "undefined" && this.jsoneditor.options.no_additional_properties && !schema.oneOf && !schema.anyOf) { schema.additionalProperties = false; } // `additionalProperties` if(typeof schema.additionalProperties !== "undefined") { for(i in value) { if(!value.hasOwnProperty(i)) continue; if(!validated_properties[i]) { // No extra properties allowed if(!schema.additionalProperties) { errors.push({ path: path, property: 'additionalProperties', message: this.translate('error_additional_properties', [i]) }); break; } // Allowed else if(schema.additionalProperties === true) { break; } // Must match schema // TODO: incompatibility between version 3 and 4 of the spec else { errors = errors.concat(this._validateSchema(schema.additionalProperties,value[i],path+'.'+i)); } } } } // `dependencies` if(schema.dependencies) { for(i in schema.dependencies) { if(!schema.dependencies.hasOwnProperty(i)) continue; // Doesn't need to meet the dependency if(typeof value[i] === "undefined") continue; // Property dependency if(Array.isArray(schema.dependencies[i])) { for(j=0; j<schema.dependencies[i].length; j++) { if(typeof value[schema.dependencies[i][j]] === "undefined") { errors.push({ path: path, property: 'dependencies', message: this.translate('error_dependency', [schema.dependencies[i][j]]) }); } } } // Schema dependency else { errors = errors.concat(this._validateSchema(schema.dependencies[i],value,path)); } } } } // Custom type validation (global) $each(JSONEditor.defaults.custom_validators,function(i,validator) { errors = errors.concat(validator.call(self,schema,value,path)); }); // Custom type validation (instance specific) if(this.options.custom_validators) { $each(this.options.custom_validators,function(i,validator) { errors = errors.concat(validator.call(self,schema,value,path)); }); } return errors; }, _checkType: function(type, value) { // Simple types if(typeof type === "string") { if(type==="string") return typeof value === "string"; else if(type==="number") return typeof value === "number"; else if(type==="integer") return typeof value === "number" && value === Math.floor(value); else if(type==="boolean") return typeof value === "boolean"; else if(type==="array") return Array.isArray(value); else if(type === "object") return value !== null && !(Array.isArray(value)) && typeof value === "object"; else if(type === "null") return value === null; else return true; } // Schema else { return !this._validateSchema(type,value).length; } } });