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);
+
+ });
});