diff --git a/CHANGELOG.md b/CHANGELOG.md index d29bbbf..6bf9773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ + + +# 1.20.1 (2026-07-08) + +## Changes +- Added filter option to array validator + # 1.20.0 (2026-04-01) diff --git a/README.md b/README.md index d78b82a..e987756 100644 --- a/README.md +++ b/README.md @@ -524,6 +524,61 @@ check({ roles: "user" }); // Valid // After both validation: roles = ["user"] ``` +**Example for `filter`:** + +```js +// *********Filter undefined values +const schema = { + roles: { type: "array", filter: "undefined" } +} +const check = v.compile(schema); + +check({ roles: ["user", undefined, null, "employer"] }); // Valid +// After validation: roles = ["user", null, "employer"] + +// *********Filter null values +const schema = { + roles: { type: "array", filter: "null" } +} +const check = v.compile(schema); + +check({ roles: ["user", undefined, null, "employer"] }); // Valid +// After validation: roles = ["user", undefined, "employer"] + +// *********Filter undefined and null values +const schema = { + roles: { type: "array", filter: "nullish" } +} +const check = v.compile(schema); + +check({ roles: ["user", undefined, null, "employer"] }); // Valid +// After validation: roles = ["user", "employer"] + + +// *********Filter with custom function +const schema = { + roles: { + type: "array", + filter: (schema, field, parent, context) => { + const data = context.data[field]; + let i = 0; + while (i < data.length) { + if (data[i] === null || data[i] === undefined || data[i] === "user") { + data.splice(i, 1); + } else { + ++i; + } + } + return data; + }, + } +} +const check = v.compile(schema); + +check({ roles: ["user", undefined, null, "employer"] }); // Valid +// After validation: roles = ["employer"] +``` + ### Properties Property | Default | Description -------- | -------- | ----------- @@ -536,6 +591,7 @@ Property | Default | Description `enum` | `null` | Every element must be an element of the `enum` array. `items` | `null` | Schema for array items. `convert`| `null` | Wrap value into array if different type provided +`filter`| `null` | Filter array items (valid values: "undefined", "null", "nullish", function). ## `boolean` This is a `Boolean` validator. diff --git a/index.d.ts b/index.d.ts index f9e9b69..b19d421 100644 --- a/index.d.ts +++ b/index.d.ts @@ -82,7 +82,11 @@ export interface RuleArray extends RuleCustom { /** * Wrap value into array if different type provided */ - convert?: boolean + convert?: boolean; + /** + * Filter array items + */ + filter?: "null" | "undefined" | "nullish" | (() => void); } /** diff --git a/lib/rules/array.js b/lib/rules/array.js index bf64945..b4c301e 100644 --- a/lib/rules/array.js +++ b/lib/rules/array.js @@ -84,9 +84,12 @@ module.exports = function ({ schema, messages }, path, context) { `); } + src.push(` + var arr = value; + `); + if (schema.items != null) { src.push(` - var arr = value; var parentField = field; for (var i = 0; i < arr.length; i++) { value = arr[i]; @@ -99,15 +102,59 @@ module.exports = function ({ schema, messages }, path, context) { src.push(this.compileRule(rule, context, itemPath, innerSource, "arr[i]")); src.push(` } - `); - src.push(` + `); + } + + if (schema.filter) { + const schema_filter_type = typeof schema.filter; + if (schema_filter_type === "string") { + src.push(` + const filterArr = (arr, val) => { + let i = 0; + while (i < arr.length) { + if (arr[i] === val) { + arr.splice(i, 1); + } else { + ++i; + } + } + }; + `); + switch (schema.filter) { + case "undefined": + src.push(` + filterArr(arr, undefined); + `); + break; + case "null": + src.push(` + filterArr(arr, null); + `); + break; + case "nullish": + src.push(` + filterArr(arr, null); + filterArr(arr, undefined); + `); + break; + default: + src.push(` + ${this.makeError({ type: "array", expected: "\"Valid filter value: 'undefined', 'null', 'nullable', link to function\"", actual: "\"" + schema.filter + "\"", messages })} + `); + } + + } + } + + // if (schema.filterUndefined === true) { + // src.push(` + // arr = arr.filter(x => x !== undefined); + // `); + // } + + src.push(` return arr; `); - } else { - src.push(` - return value; - `); - } return { sanitized, diff --git a/lib/validator.js b/lib/validator.js index de91084..d71232f 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -158,6 +158,17 @@ class Validator { handleNoValue = this.makeError({ type: "required", actual: "value", messages: rule.messages }); } + if (typeof rule.schema.filter === "function") { + if (!context.customs[rule.index]) context.customs[rule.index] = {}; + context.customs[rule.index].filterFn = rule.schema.filter; + const filterValue = `context.customs[${rule.index}].filterFn.call(this, context.rules[${rule.index}].schema, field, parent, context)`; + + src.push(` + value = ${filterValue}; + ${resVar} = value; + `); + } + src.push(` ${`if (value === undefined) { ${skipUndefinedValue ? "\n// allow undefined\n" : handleNoValue} }`} diff --git a/package.json b/package.json index eb223d2..186ec0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastest-validator", - "version": "1.19.1", + "version": "1.20.1", "description": "The fastest JS validator library for NodeJS", "main": "index.js", "browser": "dist/index.min.js", diff --git a/test/rules/array.spec.js b/test/rules/array.spec.js index ccb7dd0..8b1427a 100644 --- a/test/rules/array.spec.js +++ b/test/rules/array.spec.js @@ -281,4 +281,84 @@ describe("Test rule: array", () => { { type: "string", field: "[3]", actual: true, message: "The '[3]' field must be a string." } ]); }); + + it("should error for wrong filter name ", async () => { + const schema = { + arr: { + type: "array", + filter: "unddfd", + }, + }; + const check = v.compile(schema); + const arr = [1]; + expect(check({arr})).toEqual([{"actual": "unddfd", "expected": "Valid filter value: 'undefined', 'null', 'nullable', link to function", "field": "arr", "message": "The 'arr' field must be an array.", "type": "array"}]); + }); + + it("should filter undefined values", async () => { + const schema = { + arr: { + type: "array", + filter: "undefined", + }, + }; + const check = v.compile(schema); + const arr = [1, "string", null, undefined, "abc"]; + const expected = [1, "string", null, "abc"]; + expect(check({arr})).toEqual(true); + expect(arr).toEqual(expected); + }); + + it("should filter null values", async () => { + const schema = { + arr: { + type: "array", + filter: "null", + }, + }; + const check = v.compile(schema); + const arr = [1, "string", null, undefined, "abc"]; + const expected = [1, "string", undefined, "abc"]; + expect(check({arr})).toEqual(true); + expect(arr).toEqual(expected); + }); + + it("should filter nullish values", async () => { + const schema = { + arr: { + type: "array", + filter: "nullish", + }, + }; + const check = v.compile(schema); + const arr = [1, "string", null, undefined, "abc"]; + const expected = [1, "string", "abc"]; + expect(check({arr})).toEqual(true); + expect(arr).toEqual(expected); + }); + + it("should filter with function", async () => { + const schema = { + arr: { + type: "array", + filter: (schema, field, parent, context) => { + const data = context.data[field]; + let i = 0; + while (i < data.length) { + if (data[i] === null || data[i] === undefined || data[i] === 1) { + data.splice(i, 1); + } else { + ++i; + } + } + return data; + }, + }, + }; + const check = v.compile(schema); + const arr = [1, "string", null, undefined, "abc"]; + const expected = ["string", "abc"]; + expect(check({arr})).toEqual(true); + expect(arr).toEqual(expected); + + }); });