diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b2026e5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +# Change these settings to your own preference +indent_style = tab +indent_size = 4 +space_after_anon_function = true + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false +indent_style = space +indent_size = 4 + +[{package,bower}.json] +indent_style = space +indent_size = 2 + +[*.js] +quote_type = "double" diff --git a/README.md b/README.md index b6b49f5..1a6ab6c 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ You can install it via [NPM](http://npmjs.org/). ``` $ npm install fastest-validator --save ``` -or +or ``` $ yarn add fastest-validator ``` @@ -56,7 +56,7 @@ $ yarn add fastest-validator ## Usage ### Simple method -Call the `validate` method with the `object` and the `schema`. +Call the `validate` method with the `object` and the `schema`. > If performance is important, you won't use this method. ```js @@ -76,7 +76,7 @@ console.log(v.validate({ id: 5, name: "John", status: true }, schema)); console.log(v.validate({ id: 5, name: "Al", status: true }, schema)); /* Returns an array with errors: [ - { + { type: 'stringMin', expected: 3, actual: 2, @@ -111,7 +111,7 @@ console.log(check({ id: 5, name: "John", status: true })); console.log(check({ id: 2, name: "Adam" })); /* Returns an array with errors: [ - { + { type: 'required', field: 'status', message: 'The \'status\' field is required!' @@ -187,7 +187,7 @@ v.validate({ prop: "John" }, schema); // Valid ``` ## `array` -This is an `Array` validator. +This is an `Array` validator. **Simple example with strings:** ```js @@ -226,7 +226,7 @@ let schema = { } } } -v.validate({ +v.validate({ users: [ { id: 1, name: "John", status: true }, { id: 2, name: "Jane", status: true }, @@ -259,7 +259,7 @@ v.validate({ roles: ["guest"] }, schema); // Fail ## `boolean` -This is a `Boolean` validator. +This is a `Boolean` validator. ```js let schema = { @@ -278,7 +278,7 @@ Property | Default | Description ## `date` -This is a `Date` validator. +This is a `Date` validator. ```js let schema = { @@ -295,7 +295,7 @@ Property | Default | Description `convert` | `false`| if `true` and the type is not `Date`, try to convert with `new Date()`. ## `email` -This is an e-mail address validator. +This is an e-mail address validator. ```js let schema = { @@ -313,7 +313,7 @@ Property | Default | Description `mode` | `quick` | Checker method. Can be `quick` or `precise`. ## `enum` -This is an enum validator. +This is an enum validator. ```js let schema = { @@ -332,7 +332,7 @@ Property | Default | Description ## `forbidden` -This validator returns an error if the property exists in the object. +This validator returns an error if the property exists in the object. ```js let schema = { @@ -393,15 +393,15 @@ let schema = { } } } -v.validate({ +v.validate({ address: { country: "Italy", city: "Rome", zip: 12345 - } + } }, schema); // Valid -v.validate({ +v.validate({ address: { country: "Italy", city: "Rome" @@ -440,7 +440,7 @@ Property | Default | Description ## `url` -This is an URL validator. +This is an URL validator. ```js let schema = { @@ -502,9 +502,9 @@ let v = new Validator({ const schema = { name: { type: "string", min: 3, max: 255 }, - weight: { - type: "custom", - minWeight: 10, + weight: { + type: "custom", + minWeight: 10, check(value, schema) { return (value < schema.minWeight) ? this.makeError("weightMin", schema.minWeight, value) @@ -518,12 +518,12 @@ console.log(v.validate({ name: "John", weight: 50 }, schema)); console.log(v.validate({ name: "John", weight: 8 }, schema)); /* Returns an array with errors: - [{ - type: 'weightMin', - expected: 10, - actual: 8, - field: 'weight', - message: 'The weight must be greater than 10! Actual: 8' + [{ + type: 'weightMin', + expected: 10, + actual: 8, + field: 'weight', + message: 'The weight must be greater than 10! Actual: 8' }] */ ``` @@ -542,18 +542,61 @@ const v = new Validator({ v.validate({ name: "John" }, { name: { type: "string", min: 6 }}); /* Returns: -[ - { +[ + { type: 'stringMin', expected: 6, actual: 4, field: 'name', - message: 'A(z) \'name\' mező túl rövid. Minimum: 6, Jelenleg: 4' - } + message: 'A(z) \'name\' mező túl rövid. Minimum: 6, Jelenleg: 4' + } ] */ ``` +# Personalised Messages +Sometimes the standard messages are too generic. You can customise messages per validation type per field: +```js +const Validator = require("fastest-validator"); +const v = new Validator(); +const schema = { + firstname: { + type: "string", + min: 6, + messages: { + string: "Please check your firstname", + stringMin: "Your firstname is too short" + } + }, + lastname: { + type: "string", + min: 6, + messages: { + string: "Please check your lastname", + stringMin: "Your lastname is too short" + } + } +} +v.validate({ firstname: "John", lastname: 23 }, schema ); +/* Returns: +[ + { + type: 'stringMin', + expected: 6, + actual: 4, + field: 'firstname', + message: 'Your firstname is too short' + }, + { + type: 'string', + expected: undefined, + actual: undefined, + field: 'lastname', + message: 'Please check your lastname' + } +] +*/ +``` ## Message types Name | Default text ------------------- | ------------- diff --git a/benchmark/suites/simple.js b/benchmark/suites/simple.js index a6482af..04ff9fe 100644 --- a/benchmark/suites/simple.js +++ b/benchmark/suites/simple.js @@ -32,7 +32,27 @@ const schema = { }, email: { type: "email" }, firstName: { type: "string" }, - phone: { type: "string" }, + phone: { type: "string"}, + age: { + type: "number", + min: 18 + } +}; + +const schema2 = { + name: { + type: "string", + min: 4, + max: 25, + messages: { + string: "Csak szöveges érték", + stringMin: "Túl rövid!", + stringMax: "Túl hosszú" + } + }, + email: { type: "email" }, + firstName: { type: "string" }, + phone: { type: "string"}, age: { type: "number", min: 18 @@ -40,22 +60,27 @@ const schema = { }; bench.ref("compile & validate", () => { - let res = v.validate(obj, schema); + const res = v.validate(obj, schema); if (res !== true) throw new Error("Validation error!", res); }); +bench.add("compile & validate with custom messages", () => { + const res = v.validate(obj, schema2); + if (res !== true) + throw new Error("Validation error!", res); +}); -let check = v.compile(schema); +const check = v.compile(schema); bench.add("validate with pre-compiled schema", () => { - let res = check(obj); + const res = check(obj); if (res !== true) throw new Error("Validation error!", res); }); bench.add("validate with wrong obj", () => { - let res = check(wrongObj); + const res = check(wrongObj); if (res === true) throw new Error("Validation error!", res); }); @@ -86,4 +111,4 @@ Suite: Simple object validate with wrong obj -36.79% (704,992 rps) (avg: 1μs) ----------------------------------------------------------------------- -*/ \ No newline at end of file +*/ diff --git a/lib/rules/email.js b/lib/rules/email.js index 3345d41..b207104 100644 --- a/lib/rules/email.js +++ b/lib/rules/email.js @@ -1,6 +1,6 @@ "use strict"; -const PRECISE_PATTERN = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; +const PRECISE_PATTERN = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; const BASIC_PATTERN = /^\S+@\S+\.\S+$/; module.exports = function checkEmail(value, schema) { @@ -13,7 +13,7 @@ module.exports = function checkEmail(value, schema) { pattern = PRECISE_PATTERN; else pattern = BASIC_PATTERN; - + if (!pattern.test(value)) { return this.makeError("email"); } diff --git a/lib/rules/string.js b/lib/rules/string.js index 2f3b5b2..ee275f5 100644 --- a/lib/rules/string.js +++ b/lib/rules/string.js @@ -3,7 +3,7 @@ const NUMERIC_PATTERN = /^-?[0-9]\d*(\.\d+)?$/; const ALPHA_PATTERN = /^[a-zA-Z]+$/; const ALPHANUM_PATTERN = /^[a-zA-Z0-9]+$/; -const ALPHADASH_PATTERN = /^[a-zA-Z0-9_\-]+$/; +const ALPHADASH_PATTERN = /^[a-zA-Z0-9_-]+$/; module.exports = function checkString(value, schema) { if (typeof value !== "string") { @@ -26,7 +26,7 @@ module.exports = function checkString(value, schema) { if (schema.length != null && valueLength !== schema.length) { return this.makeError("stringLength", schema.length, valueLength); - } + } if (schema.pattern != null) { const pattern = typeof schema.pattern == "string" ? new RegExp(schema.pattern, schema.patternFlags) : schema.pattern; @@ -36,7 +36,7 @@ module.exports = function checkString(value, schema) { if (schema.contains != null && value.indexOf(schema.contains) === -1) { return this.makeError("stringContains", schema.contains); - } + } if (schema.enum != null && schema.enum.indexOf(value) === -1) { return this.makeError("stringEnum", schema.enum); @@ -59,4 +59,4 @@ module.exports = function checkString(value, schema) { } return true; -}; \ No newline at end of file +}; diff --git a/lib/validator.js b/lib/validator.js index cc7d5a8..f1809f8 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -31,7 +31,7 @@ const escapeEvalRegex = /["'\\\n\r\u2028\u2029]/g; function escapeEvalString(str) { // Based on https://github.com/joliss/js-string-escape - return str.replace(escapeEvalRegex, function (character) { + return str.replace(escapeEvalRegex, function(character) { switch (character) { case "\"": case "'": @@ -64,6 +64,7 @@ function Validator(opts) { deepExtend(this.opts, opts); this.messages = this.opts.messages; + this.messageKeys = Object.keys(this.messages); // Load rules this.rules = loadRules(); @@ -90,7 +91,7 @@ Validator.prototype.validate = function(obj, schema) { Validator.prototype.compile = function(schema) { const self = this; if (Array.isArray(schema)) { - // Multiple schemas + // Multiple schemas if (schema.length == 0) { throw new Error("If the schema is an Array, must contain at least one element!"); } @@ -100,7 +101,7 @@ Validator.prototype.compile = function(schema) { return function(value, path, parent) { return self.checkSchemaType(value, rules, path, parent || null); }; - } + } const rule = this.compileSchemaObject(schema); this.cache.clear(); @@ -118,14 +119,14 @@ Validator.prototype.compileSchemaObject = function(schemaObject) { if (compiledObject) { compiledObject.cycle = true; return compiledObject; - } else{ + } else { compiledObject = { cycle: false, properties: null, compiledObjectFunction: null, objectStack: [] }; this.cache.set(schemaObject, compiledObject); } compiledObject.properties = Object.keys(schemaObject).map(name => { const compiledType = this.compileSchemaType(schemaObject[name]); - return {name: name, compiledType: compiledType}; + return { name: name, compiledType: compiledType }; }); const sourceCode = []; @@ -144,7 +145,7 @@ Validator.prototype.compileSchemaObject = function(schemaObject) { sourceCode.push(`res = this.checkSchemaRule(${propertyValueExpr}, properties[${i}].compiledType, propertyPath, value);`); } sourceCode.push("if (res !== true) {"); - sourceCode.push("\tthis.handleResult(errors, propertyPath, res);"); + sourceCode.push(`\tthis.handleResult(errors, propertyPath, res, properties[${i}].compiledType.messages);`); sourceCode.push("}"); } @@ -156,9 +157,12 @@ Validator.prototype.compileSchemaObject = function(schemaObject) { }; Validator.prototype.compileSchemaType = function(schemaType) { + if (Array.isArray(schemaType)) { + // Multiple rules, flatten to array of compiled SchemaRule const rules = flatten(schemaType.map(r => this.compileSchemaType(r))); + if (rules.length == 1) { return rules[0]; } @@ -167,9 +171,23 @@ Validator.prototype.compileSchemaType = function(schemaType) { } return this.compileSchemaRule(schemaType); + +}; + +Validator.prototype.compileMessages = function(schemaType) { + + if (schemaType.messages) { + return this.messageKeys.reduce((a, key) => { + a[key] = schemaType.messages[key] || this.messages[key]; + return a; + }, {}); + } + + return this.messages; }; Validator.prototype.compileSchemaRule = function(schemaRule) { + if (typeof schemaRule === "string") { schemaRule = { type: schemaRule @@ -177,10 +195,13 @@ Validator.prototype.compileSchemaRule = function(schemaRule) { } const ruleFunction = this.rules[schemaRule.type]; + if (!ruleFunction) { throw new Error("Invalid '" + schemaRule.type + "' type in validator schema!"); } + const messages = this.compileMessages(schemaRule); + let dataParameter = null; let dataFunction = null; @@ -193,6 +214,7 @@ Validator.prototype.compileSchemaRule = function(schemaRule) { } return { + messages: messages, schemaRule: schemaRule, ruleFunction: ruleFunction, dataFunction: dataFunction, @@ -219,26 +241,28 @@ Validator.prototype.checkSchemaObjectInner = function(value, compiledObject, pat return compiledObject.compiledObjectFunction.call(this, value, compiledObject.properties, path, parent); /* - // Reference implementation of the object checker - - const errors = []; - const propertiesLength = compiledObject.properties.length; - for (let i = 0; i < propertiesLength; i++) { - const property = compiledObject.properties[i]; - const propertyPath = (path !== undefined ? path + "." : "") + property.name; - const res = this.checkSchemaType(value[property.name], property.compiledType, propertyPath, value); - - if (res !== true) { - this.handleResult(errors, propertyPath, res); - } - } - - return errors.length === 0 ? true : errors; - */ + // Reference implementation of the object checker + + const errors = []; + const propertiesLength = compiledObject.properties.length; + for (let i = 0; i < propertiesLength; i++) { + const property = compiledObject.properties[i]; + const propertyPath = (path !== undefined ? path + "." : "") + property.name; + const res = this.checkSchemaType(value[property.name], property.compiledType, propertyPath, value); + + if (res !== true) { + this.handleResult(errors, propertyPath, res); + } + } + + return errors.length === 0 ? true : errors; + */ }; Validator.prototype.checkSchemaType = function(value, compiledType, path, parent) { + if (Array.isArray(compiledType)) { + const errors = []; const checksLength = compiledType.length; for (let i = 0; i < checksLength; i++) { @@ -246,7 +270,7 @@ Validator.prototype.checkSchemaType = function(value, compiledType, path, parent const res = this.checkSchemaRule(value, compiledType[i], path, parent); if (res !== true) { - this.handleResult(errors, path, res); + this.handleResult(errors, path, res, compiledType.messages); } else { // Jump out after first success and clear previous errors return true; @@ -254,7 +278,7 @@ Validator.prototype.checkSchemaType = function(value, compiledType, path, parent } return errors; - } + } return this.checkSchemaRule(value, compiledType, path, parent); }; @@ -268,7 +292,7 @@ Validator.prototype.checkSchemaArray = function(value, compiledArray, path, pare const res = this.checkSchemaType(value[i], compiledArray, itemPath, value, parent); if (res !== true) { - this.handleResult(errors, itemPath, res); + this.handleResult(errors, itemPath, res, compiledArray.messages); } } @@ -286,14 +310,17 @@ Validator.prototype.checkSchemaRule = function(value, compiledRule, path, parent return true; const errors = []; - this.handleResult(errors, path, this.makeError("required")); + this.handleResult(errors, path, this.makeError("required"), compiledRule.messages); + return errors; } const res = compiledRule.ruleFunction.call(this, value, schemaRule, path, parent); + if (res !== true) { const errors = []; - this.handleResult(errors, path, res); + this.handleResult(errors, path, res, compiledRule.messages); + return errors; } @@ -311,7 +338,7 @@ Validator.prototype.checkSchemaRule = function(value, compiledRule, path, parent * @param {String} fieldPath * @param {Array|Object} res */ -Validator.prototype.handleResult = function(errors, fieldPath, res) { +Validator.prototype.handleResult = function(errors, fieldPath, res, messages) { let items; if (!Array.isArray(res)) items = [res]; @@ -322,7 +349,7 @@ Validator.prototype.handleResult = function(errors, fieldPath, res) { if (!err.field) err.field = fieldPath; if (!err.message) - err.message = this.resolveMessage(err); + err.message = this.resolveMessage(err, messages[err.type]); errors.push(err); }); @@ -348,8 +375,8 @@ Validator.prototype.makeError = function(type, expected, actual) { * * @param {Object} err Validation error object */ -Validator.prototype.resolveMessage = function(err) { - let msg = this.messages[err.type]; +Validator.prototype.resolveMessage = function(err, msg = null) { + if (msg != null) { const expected = err.expected != null ? err.expected : ""; const actual = err.actual != null ? err.actual : ""; diff --git a/package.json b/package.json index 6a2e32f..cf378dc 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "dev": "nodemon examples/index.js", "ci": "jest --watch", "test": "jest --coverage", - "lint": "eslint --ext=.js src test rollup.config.js", + "lint": "eslint --ext=.js lib test rollup.config.js", + "lint:fix": "eslint --fix --ext=.js lib test rollup.config.js", "deps": "npm-check -u", "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" }, diff --git a/test/rules/custom_messages.spec.js b/test/rules/custom_messages.spec.js new file mode 100644 index 0000000..8af661b --- /dev/null +++ b/test/rules/custom_messages.spec.js @@ -0,0 +1,101 @@ +"use strict"; + +const Validator = require("../../lib/validator"); +const v = new Validator(); + +describe("Test custom messages", () => { + + it("should give back not a string message", () => { + + const message = "That wasn't a string!"; + const s = { name: { type: "string", messages: { string: message } } }; + + expect(v.validate({ name: 123 }, s)).toEqual([{ type: "string", expected: undefined, actual: undefined, field: "name", message }]); + + }); + + it("should give back required message", () => { + + const message = "Your name is required!"; + + const s = { name: { type: "string", messages: { required: message } } }; + + expect(v.validate({}, s)).toEqual([{ type: "required", expected: undefined, actual: undefined, field: "name", message: message }]); + + }); + + it("should do replacements in custom messages", () => { + + const message = "Incorrect name length. Your field: {field} had {actual} chars when it should have no more than {expected}"; + const s = { name: { type: "string", max: 2, messages: { stringMax: message } } }; + + expect(v.validate({ name: "Long string" }, s)).toEqual([{ type: "stringMax", expected: 2, actual: 11, field: "name", message: "Incorrect name length. Your field: name had 11 chars when it should have no more than 2" }]); + + }); + + it("should do custom messages in arrays", () => { + + const s = { + cache: [ + { type: "string", messages: { string: "Not a string" } }, + { type: "boolean", messages: { boolean: "Not a boolean" } } + ] + }; + + expect(v.validate({ cache: 123 }, s)).toEqual([{ type: "string", field: "cache", message: "Not a string" }, { type: "boolean", field: "cache", message: "Not a boolean" }]); + + }); + + it("should do custom messages in objects", () => { + + const s = { + users: { + type: "array", + items: { + type: "object", + props: { + id: { type: "number", positive: true, messages: { "number": "numbers only please" } }, + name: { type: "string", empty: false, messages: { "string": "make sure it's a string" } }, + status: "boolean" + } + } + } + }; + + expect(v.validate({ + users: [ + { id: "test", name: "John", status: true }, + { id: 2, name: 123, status: true }, + { id: 3, name: "Bill", status: false } + ] + }, s)).toEqual([{ type: "number", field: "users[0].id", message: "numbers only please" }, { type: "string", field: "users[1].name", message: "make sure it's a string" }]); + + }); + + it("should do custom messages when compiled", () => { + const s = { + users: { + type: "array", + items: { + type: "object", + props: { + id: { type: "number", positive: true, messages: { "number": "numbers only please" } }, + name: { type: "string", empty: false, messages: { "string": "make sure it's a string" } }, + status: "boolean" + } + } + } + }; + + const check = v.compile(s); + + expect(check({ + users: [ + { id: "test", name: "John", status: true }, + { id: 2, name: 123, status: true }, + { id: 3, name: "Bill", status: false } + ] + })).toEqual([{ type: "number", field: "users[0].id", message: "numbers only please" }, { type: "string", field: "users[1].name", message: "make sure it's a string" }]); + + }); +}); diff --git a/test/validator.spec.js b/test/validator.spec.js index 195ccae..7d0a515 100644 --- a/test/validator.spec.js +++ b/test/validator.spec.js @@ -110,18 +110,18 @@ describe("Test resolveMessage", () => { const v = new Validator(); it("should resolve variables in message string", () => { - let res = v.resolveMessage({ type: "stringLength", field: "age", expected: 3, actual: 6 }); + let res = v.resolveMessage({ type: "stringLength", field: "age", expected: 3, actual: 6 }, v.messages["stringLength"] ); expect(res).toBe("The 'age' field length must be 3 characters long!"); }); it("should resolve 0 value in message string", () => { - let res = v.resolveMessage({ type: "numberNotEqual", field: "b", expected: 0, actual: 0 }); + let res = v.resolveMessage({ type: "numberNotEqual", field: "b", expected: 0, actual: 0 }, v.messages["numberNotEqual"] ); expect(res).toBe("The 'b' field can't be equal with 0!"); }); it("should resolve more variables in message string", () => { v.messages.custom = "Field {field} and again {field}. Expected: {expected}, actual: {actual}."; - let res = v.resolveMessage({ type: "custom", field: "country", expected: "London", actual: 350 }); + let res = v.resolveMessage({ type: "custom", field: "country", expected: "London", actual: 350 }, v.messages.custom ); expect(res).toBe("Field country and again country. Expected: London, actual: 350."); }); @@ -354,7 +354,7 @@ describe("Test compile (integration test)", () => { it("when schema is defined as an object, and custom path is specified, it should be forwarded to validators", () => { // Note: as the item we validate always must be an object, there is no use - // of specifying a custom parent, like for the schema-as-array above. + // of specifying a custom parent, like for the schema-as-array above. // The parent is currently used in the validator code (only forwarded to the generated // function that validates all properties) and there is no way to test it. const v = new Validator(); @@ -687,19 +687,19 @@ describe("Test multiple rules with objects", () => { let schema = { list: [ - { + { type: "object", props: { name: {type: "string"}, age: {type: "number"}, - } + } }, - { + { type: "object", props: { country: {type: "string"}, code: {type: "string"}, - } + } } ] }; @@ -768,19 +768,19 @@ describe("Test multiple rules with objects within array", () => { list: { type: "array", items: [ - { + { type: "object", props: { name: {type: "string"}, age: {type: "number"}, - } + } }, - { + { type: "object", props: { country: {type: "string"}, code: {type: "string"}, - } + } } ] } @@ -907,9 +907,9 @@ describe("Test multiple rules with mixed types", () => { expect(res).toBeInstanceOf(Array); expect(res.length).toBe(2); expect(res[0].type).toBe("string"); - expect(res[0].field).toBe("value"); + expect(res[0].field).toBe("value"); expect(res[1].type).toBe("boolean"); - expect(res[1].field).toBe("value"); + expect(res[1].field).toBe("value"); }); it("should give error if 'undefined'", () => { @@ -919,9 +919,9 @@ describe("Test multiple rules with mixed types", () => { expect(res).toBeInstanceOf(Array); expect(res.length).toBe(2); expect(res[0].type).toBe("required"); - expect(res[0].field).toBe("value"); + expect(res[0].field).toBe("value"); expect(res[1].type).toBe("required"); - expect(res[1].field).toBe("value"); + expect(res[1].field).toBe("value"); }); }); @@ -931,13 +931,13 @@ describe("Test multiple rules with arrays", () => { let schema = { list: [ - { + { type: "array", - items: "string" + items: "string" }, - { + { type: "array", - items: "number" + items: "number" } ] }; @@ -994,13 +994,13 @@ describe("Test multiple array in root", () => { const v = new Validator(); let schema = [ - { + { type: "array", - items: "string" + items: "string" }, - { + { type: "array", - items: "number" + items: "number" } ];