;(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = factory();
} else {
global.JSONSchema = factory();
}
})(this, function () {
// Валидация данных в формате JSON согласно схеме проверки.
// Допустимые для типы значений:
// - types - массив с возможными вариантами типов значений
// - type - тип значения: null, boolean, number, string, array, object
// - min - минимальная длина значения или число элементов, входящих в него
// - max - максимальная длина значения или число элементов, входящих в него
// - regexp: {pattern: 'abcde', flags: 'gi'} - регулярное выражение для проверки содержимого строки
// - items - объект, содержащий в себе описание схемы для проверки элементов внутри массива, имеющих числовые индексы
// - allItems - массив, содержащий в себе описание схемы для проверки всех элементов внутри массива, имеющих одинаковый тип
// - properties - объект, содержащий в себе описание схемы для проверки элементов внутри объекта
// - optional: true - булево значение, всегда равное true и указывающее на то, что не является ошибкой, если данный элемент будет отсутствовать
//
// undefined:
// - type
// - optional: true
//
// null:
// - type
// - optional: true
//
// boolean:
// - type
// - optional: true
//
// number:
// - type
// - min
// - max
// - optional: true
//
// string:
// - type
// - min
// - max
// - regexp
// - optional: true
//
// array:
// - type
// - min
// - max
// - items
// - allItems
// - optional: true
//
// object:
// - type
// - min
// - max
// - properties
// - optional: true
// Пример использования:
//
// var data = {
// undef: undefined
// , nul: null
// , bool: false
// , num: 5
// , str: 'ab1c2de'
// , arr: [1, 2, [3, 4]]
// , obj: {one: '', two: {three: 3}}
// , anyTypeFrom: 5
// , all: [1, 2, 3, 4]
// };
//
// var schema = {
// undef: {type: 'undefined'}
// , nul: {type: 'null'}
// , bool: {type: 'boolean'}
// , num: {type: 'number', min: 0, max: 10}
// , str: {type: 'string', min: 0, max: 10, regexp: {pattern: 'abcde', flags: 'gi'}}
// , arr: {type: 'array', min: 0, max: 10, items: {
// '0': {type: 'number'}
// , '1': {type: 'number'}
// , '2': {type: 'array', items: {
// '0': {type: 'number'}
// , '1': {type: 'string'}
// }
// }
// }
// }
// , obj: {type: 'object', min: 0, max: 10, properties: {
// one: {type: 'number'}
// , two: {type: 'object', properties: {
// three: {type: 'string'}
// , four: {type: 'string', optional: true}
// }
// }
// }
// }
// , anyTypeFrom: {
// types: [
// {type: 'number', min: 0, max: 10}
// , {type: 'string', min: 0, max: 10, regexp: {pattern: 'abcde', flags: 'gi'}}
// ]
// }
// , all: {type: 'array', allItems: {type: 'number'}}
// , notExist1: {type: 'array', optional: true}
// , notExist2: {
// types: [
// {type: 'number', min: 0, max: 10}
// , {type: 'string', min: 0, max: 10, regexp: {pattern: 'abcde', flags: 'gi'}}
// ]
// , optional: true
// }
// };
//
// var result = JSONSchema.validate(data, schema);
//
// if (!result.valid) {
// for (var i = 0, len = result.errors.length; i < len; i++) {
// console.log('Ошибка: ' + result.errors[i].message);
// console.log('Путь до элемента: ' + result.errors[i].path);
// }
// }
function has (object, key) {
return Object.prototype.hasOwnProperty.call(object, key);
}
function isTypeOf (value, type) {
return Object.prototype.toString.call(value).toLowerCase().slice(8, -1) === type;
}
function objectLength (object) {
var length = 0
, key;
for (key in object) {if (has(object, key)) {
length++;
}}
return length;
}
function validate (json, schema) {
var result = {
valid: true
, errors: []
};
function addError (path, message) {
result.valid = false;
result.errors.push({path: path, message: message});
}
if (!isTypeOf(json, 'object')) {
addError('корневой элемент', 'Корневой элемент должен быть объектом.');
} else {
for (var key in schema) {if (has(schema, key)) {
if (
!has(json, key)
&& (
(has(schema[key], 'type') && schema[key].type !== 'undefined' && !has(schema[key], 'optional'))
|| (has(schema[key], 'types') && !has(schema[key], 'optional'))
)
) {
addError('корневой объект', 'Элемент "' + key + '" должен присутствовать в корневом объекте.');
}
if (has(schema[key], 'type')) {validateSingleRootType(json, schema, key);
} else if (has(schema[key], 'types')) {validateManyRootTypes(json, schema, key);
}
}}
}
function validateSingleRootType (json, schema, key) {
if (!has(schema[key], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента "' + key + '" корневого объекта.');}
if (schema[key].type === 'undefined') {validateUndefined(key, json[key], schema[key], '');
} else if (schema[key].type === 'null') {validateNull(key, json[key], schema[key], '');
} else if (schema[key].type === 'boolean') {validateBoolean(key, json[key], schema[key], '');
} else if (schema[key].type === 'number') {validateNumber(key, json[key], schema[key], '');
} else if (schema[key].type === 'string') {validateString(key, json[key], schema[key], '');
} else if (schema[key].type === 'array') {validateArray(key, json[key], schema[key], '');
} else if (schema[key].type === 'object') {validateObject(key, json[key], schema[key], '');
}
}
function validateManyRootTypes (json, schema, key) {
var elementType
, requiredElements = [];
for (var i = 0, len = schema[key].types.length; i < len; i++) {
if (!has(schema[key].types[i], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента "' + key + '" корневого объекта.');}
if (schema[key].types[i].type === 'undefined' && isTypeOf(json[key], 'undefined')) {elementType = 'undefined'; break;
} else if (schema[key].types[i].type === 'null' && isTypeOf(json[key], 'null')) {elementType = 'null'; break;
} else if (schema[key].types[i].type === 'boolean' && isTypeOf(json[key], 'boolean')) {elementType = 'boolean'; break;
} else if (schema[key].types[i].type === 'number' && isTypeOf(json[key], 'number')) {elementType = 'number'; validateNumber(key, json[key], schema[key].types[i], ''); break;
} else if (schema[key].types[i].type === 'string' && isTypeOf(json[key], 'string')) {elementType = 'string'; validateString(key, json[key], schema[key].types[i], ''); break;
} else if (schema[key].types[i].type === 'array' && isTypeOf(json[key], 'array')) {elementType = 'array'; validateArray(key, json[key], schema[key].types[i], ''); break;
} else if (schema[key].types[i].type === 'object' && isTypeOf(json[key], 'object')) {elementType = 'object'; validateObject(key, json[key], schema[key].types[i], ''); break;
} else {requiredElements.push('"' + schema[key].types[i].type + '"');
}
}
if (elementType === undefined && !has(schema[key], 'optional')) {addError('корневой объект', 'Элемент "' + key + '" корневого объекта должен иметь значение с типом: ' + requiredElements.join(', ') + '.');}
}
function validateUndefined (/*key, value, schemaForUndefined, pathToElement*/) {
// Сообщение об ошибке выводить не нужно. Если элемента нет, то это допустимо.
// if (!isTypeOf(value, 'undefined') && !has(schemaForNull, 'optional')) {addError(pathToElement + key, 'Элемент "' + key + '" не должен присутствовать.');}
}
function validateNull (key, value, schemaForNull, pathToElement) {
if (isTypeOf(value, 'undefined') && has(schemaForNull, 'optional')) {return;}
if (!isTypeOf(value, 'null')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь значение null.');}
}
function validateBoolean (key, bool, schemaForBoolean, pathToElement) {
if (isTypeOf(bool, 'undefined') && has(schemaForBoolean, 'optional')) {return;}
if (!isTypeOf(bool, 'boolean')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь значение true или false.');}
}
function validateNumber (key, number, schemaForNumber, pathToElement) {
if (isTypeOf(number, 'undefined') && has(schemaForNumber, 'optional')) {return;}
if (!isTypeOf(number, 'number')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь в качестве значения число.');
} else {
if (number !== number) {addError(pathToElement + key, 'Элемент "' + key + '" не должен иметь в качестве значения NaN.');}
if (has(schemaForNumber, 'min') && number < schemaForNumber.min) {addError(pathToElement + key, 'Значение элемента "' + key + '" должно быть больше или равно ' + schemaForNumber.min + '.');}
if (has(schemaForNumber, 'max') && number > schemaForNumber.max) {addError(pathToElement + key, 'Значение элемента "' + key + '" должно быть меньше или равно ' + schemaForNumber.max + '.');}
}
}
function validateString (key, string, schemaForString, pathToElement) {
if (isTypeOf(string, 'undefined') && has(schemaForString, 'optional')) {return;}
if (!isTypeOf(string, 'string')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь в качестве значения строку.');
} else {
if (has(schemaForString, 'min') && schemaForString.min === 0 && string.length === 0) {return;}
if (has(schemaForString, 'min') && string.length < schemaForString.min) {addError(pathToElement + key, 'Число символов в строке "' + key + '" должно быть больше или равно ' + schemaForString.min + '.');}
if (has(schemaForString, 'max') && string.length > schemaForString.max) {addError(pathToElement + key, 'Число символов в строке "' + key + '" должно быть меньше или равно ' + schemaForString.max + '.');}
if (has(schemaForString, 'regexp') && !(new RegExp(schemaForString.regexp.pattern, schemaForString.regexp.flags).test(string))) {addError(pathToElement + key, 'Значение элемента "' + key + '" не соответствует регулярному выражению: new RegExp("' + schemaForString.regexp.pattern + '", "' + schemaForString.regexp.flags + '")');}
}
}
function validateArray (key, array, schemaForArray, pathToElement) {
var index
, arrayLength;
if (isTypeOf(array, 'undefined') && has(schemaForArray, 'optional')) {return;}
if (!isTypeOf(array, 'array')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь в качестве значения массив.');
} else {
if (has(schemaForArray, 'min') && schemaForArray.min === 0 && array.length === 0) {return;}
if (has(schemaForArray, 'min') && array.length < schemaForArray.min) {addError(pathToElement + key, 'Число элементов в массиве "' + key + '" должно быть больше или равно ' + schemaForArray.min + '.');}
if (has(schemaForArray, 'max') && array.length > schemaForArray.max) {addError(pathToElement + key, 'Число элементов в массиве "' + key + '" должно быть меньше или равно ' + schemaForArray.max + '.');}
if (has(schemaForArray, 'items')) {
for (index in schemaForArray.items) {if (has(schemaForArray.items, index)) {
if (array[index] === undefined) {addError(pathToElement + key, 'Элемент с индексом "' + index + '" должен присутствовать в массиве "' + key + '".');}
if (has(schemaForArray.items[index], 'type')) {validateSingleArrayType(array, schemaForArray, index, key, pathToElement);
} else if (has(schemaForArray.items[index], 'types')) {validateManyArrayTypes(array, schemaForArray, index, key, pathToElement);
}
}}
}
if (has(schemaForArray, 'allItems')) {
for (index = 0, arrayLength = array.length; index < arrayLength; index++) {
if (!has(schemaForArray.allItems, 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для всех элементов массива "' + key + '".');}
if (schemaForArray.allItems.type === 'undefined') {validateUndefined(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
} else if (schemaForArray.allItems.type === 'null') {validateNull(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
} else if (schemaForArray.allItems.type === 'boolean') {validateBoolean(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
} else if (schemaForArray.allItems.type === 'number') {validateNumber(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
} else if (schemaForArray.allItems.type === 'string') {validateString(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
} else if (schemaForArray.allItems.type === 'array') {validateArray(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
} else if (schemaForArray.allItems.type === 'object') {validateObject(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
}
}
}
}
}
function validateSingleArrayType (array, schemaForArray, index, key, pathToElement) {
if (!has(schemaForArray.items[index], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента с индексом "' + index + '" массива "' + key + '".');}
if (schemaForArray.items[index].type === 'undefined') {validateUndefined(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
} else if (schemaForArray.items[index].type === 'null') {validateNull(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
} else if (schemaForArray.items[index].type === 'boolean') {validateBoolean(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
} else if (schemaForArray.items[index].type === 'number') {validateNumber(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
} else if (schemaForArray.items[index].type === 'string') {validateString(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
} else if (schemaForArray.items[index].type === 'array') {validateArray(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
} else if (schemaForArray.items[index].type === 'object') {validateObject(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
}
}
function validateManyArrayTypes (array, schemaForArray, index, key, pathToElement) {
var elementType
, requiredElements = [];
for (var i = 0, len = schemaForArray.items[index].types.length; i < len; i++) {
if (!has(schemaForArray.items[index].types[i], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента с индексом "' + index + '" массива "' + key + '".');}
if (schemaForArray.items[index].types[i].type === 'undefined' && isTypeOf(array[index], 'undefined')) {elementType = 'undefined'; break;
} else if (schemaForArray.items[index].types[i].type === 'null' && isTypeOf(array[index], 'null')) {elementType = 'null'; break;
} else if (schemaForArray.items[index].types[i].type === 'boolean' && isTypeOf(array[index], 'boolean')) {elementType = 'boolean'; break;
} else if (schemaForArray.items[index].types[i].type === 'number' && isTypeOf(array[index], 'number')) {elementType = 'number'; validateNumber(index, array[index], schemaForArray.items[index].types[i], pathToElement + key + '.'); break;
} else if (schemaForArray.items[index].types[i].type === 'string' && isTypeOf(array[index], 'string')) {elementType = 'string'; validateString(index, array[index], schemaForArray.items[index].types[i], pathToElement + key + '.'); break;
} else if (schemaForArray.items[index].types[i].type === 'array' && isTypeOf(array[index], 'array')) {elementType = 'array'; validateArray(index, array[index], schemaForArray.items[index].types[i], pathToElement + key + '.'); break;
} else if (schemaForArray.items[index].types[i].type === 'object' && isTypeOf(array[index], 'object')) {elementType = 'object'; validateObject(index, array[index], schemaForArray.items[index].types[i], pathToElement + key + '.'); break;
} else {requiredElements.push('"' + schemaForArray.items[index].types[i].type + '"');
}
}
if (elementType === undefined && !has(schemaForArray.items[index], 'optional')) {addError(pathToElement + key + '.' + index, 'Элемент с индексом "' + index + '" массива "' + key + '" должен иметь значение с типом: ' + requiredElements.join(', ') + '.');}
}
function validateObject (key, object, schemaForObject, pathToElement) {
if (isTypeOf(object, 'undefined') && has(schemaForObject, 'optional')) {return;}
if (!isTypeOf(object, 'object')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь в качестве значения объект.');
} else {
if (has(schemaForObject, 'min') && schemaForObject.min === 0 && objectLength(object) === 0) {return;}
if (has(schemaForObject, 'min') && objectLength(object) < schemaForObject.min) {addError(pathToElement + key, 'Число свойств в объекте "' + key + '" должно быть больше или равно ' + schemaForObject.min + '.');}
if (has(schemaForObject, 'max') && objectLength(object) > schemaForObject.max) {addError(pathToElement + key, 'Число свойств в объекте "' + key + '" должно быть меньше или равно ' + schemaForObject.max + '.');}
if (has(schemaForObject, 'properties')) {
for (var property in schemaForObject.properties) {if (has(schemaForObject.properties, property)) {
if (
!has(object, property)
&& (
(has(schemaForObject.properties[property], 'type') && schemaForObject.properties[property].type !== 'undefined' && !has(schemaForObject.properties[property], 'optional'))
|| (has(schemaForObject.properties[property], 'types') && !has(schemaForObject.properties[property], 'optional'))
)
) {
addError(pathToElement + key, 'Элемент "' + property + '" должен присутствовать в объекте "' + key + '".');
}
if (has(schemaForObject.properties[property], 'type')) {validateSingleObjectType(object, schemaForObject, property, key, pathToElement);
} else if (has(schemaForObject.properties[property], 'types')) {validateManyObjectTypes(object, schemaForObject, property, key, pathToElement);
}
}}
}
}
}
function validateSingleObjectType (object, schemaForObject, property, key, pathToElement) {
if (!has(schemaForObject.properties[property], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента "' + property + '" объекта "' + key + '".');}
if (schemaForObject.properties[property].type === 'undefined') {validateUndefined(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
} else if (schemaForObject.properties[property].type === 'null') {validateNull(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
} else if (schemaForObject.properties[property].type === 'boolean') {validateBoolean(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
} else if (schemaForObject.properties[property].type === 'number') {validateNumber(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
} else if (schemaForObject.properties[property].type === 'string') {validateString(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
} else if (schemaForObject.properties[property].type === 'array') {validateArray(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
} else if (schemaForObject.properties[property].type === 'object') {validateObject(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
}
}
function validateManyObjectTypes (object, schemaForObject, property, key, pathToElement) {
var elementType
, requiredElements = [];
for (var i = 0, len = schemaForObject.properties[property].types.length; i < len; i++) {
if (!has(schemaForObject.properties[property].types[i], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента "' + property + '" объекта "' + key + '".');}
if (schemaForObject.properties[property].types[i].type === 'undefined' && isTypeOf(object[property], 'undefined')) {elementType = 'undefined'; break;
} else if (schemaForObject.properties[property].types[i].type === 'null' && isTypeOf(object[property], 'null')) {elementType = 'null'; break;
} else if (schemaForObject.properties[property].types[i].type === 'boolean' && isTypeOf(object[property], 'boolean')) {elementType = 'boolean'; break;
} else if (schemaForObject.properties[property].types[i].type === 'number' && isTypeOf(object[property], 'number')) {elementType = 'number'; validateNumber(property, object[property], schemaForObject.properties[property].types[i], pathToElement + key + '.'); break;
} else if (schemaForObject.properties[property].types[i].type === 'string' && isTypeOf(object[property], 'string')) {elementType = 'string'; validateString(property, object[property], schemaForObject.properties[property].types[i], pathToElement + key + '.'); break;
} else if (schemaForObject.properties[property].types[i].type === 'array' && isTypeOf(object[property], 'array')) {elementType = 'array'; validateArray(property, object[property], schemaForObject.properties[property].types[i], pathToElement + key + '.'); break;
} else if (schemaForObject.properties[property].types[i].type === 'object' && isTypeOf(object[property], 'object')) {elementType = 'object'; validateObject(property, object[property], schemaForObject.properties[property].types[i], pathToElement + key + '.'); break;
} else {requiredElements.push('"' + schemaForObject.properties[property].types[i].type + '"');
}
}
if (elementType === undefined && !has(schemaForObject.properties[property], 'optional')) {addError(pathToElement + key + '.' + property, 'Элемент "' + property + '" объекта "' + key + '" должен иметь значение с типом: ' + requiredElements.join(', ') + '.');}
}
if (result.errors.length > 0) {
(function(){
var pathElements
, pathElementsLength
, resultPath = ''
, separator
, errorsLength = result.errors.length
, i;
while (errorsLength--) {
pathElements = result.errors[errorsLength].path.split('.');
pathElementsLength = pathElements.length;
for (i = 0; i < pathElementsLength; i++) {
if (i === 0) {
separator = '';
} else {
separator = '.';
}
if ((/^\d+$/g).test(pathElements[i])) {resultPath += '[' + pathElements[i] + ']';
} else if ((/^\d+/g).test(pathElements[i])) {resultPath += '["' + pathElements[i] + '"]';
} else {resultPath += separator + pathElements[i];
}
}
result.errors[errorsLength].path = resultPath;
resultPath = '';
}
})();
}
return result;
}
return {validate: validate};
});
Комментариев нет:
Отправить комментарий