Repository: icebob/fastest-validator
Pull request #314 (2022-11-28 09:00:20)

convert property in array type


Patch:
diff --git a/README.md b/README.md
index de49a6c..f83607b 100644
--- a/README.md
+++ b/README.md
@@ -486,6 +486,19 @@ check({ roles: ["user", "admin", "user"] }); // Fail
 check({ roles: [1, 2, 1] }); // Fail
 ```
 
+**Example for `convert`:**
+
+```js
+const schema = {
+    roles: { type: "array", items: 'string', convert: true }
+}
+const check = v.compile(schema);
+
+check({ roles: ["user"] }); // Valid
+check({ roles: "user" }); // Valid
+// After both validation: roles = ["user"]
+```
+
 ### Properties
 Property | Default  | Description
 -------- | -------- | -----------
@@ -497,6 +510,7 @@ Property | Default  | Description
 `unique` | `null` | The array must be unique (array of objects is always unique).
 `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
 
 ## `boolean`
 This is a `Boolean` validator.
diff --git a/lib/rules/array.js b/lib/rules/array.js
index fdf43c3..bf64945 100644
--- a/lib/rules/array.js
+++ b/lib/rules/array.js
@@ -5,6 +5,17 @@
 module.exports = function ({ schema, messages }, path, context) {
 	const src = [];
 
+	let sanitized = false;
+	if (schema.convert === true) {
+		sanitized = true;
+		// Convert to array if not and the value is not null or undefined
+		src.push(`
+			if (!Array.isArray(value) && value != null) {
+				value = [value];
+			}
+		`);
+	}
+
 	src.push(`
 		if (!Array.isArray(value)) {
 			${this.makeError({ type: "array", actual: "value", messages })}
@@ -99,6 +110,7 @@ module.exports = function ({ schema, messages }, path, context) {
 	}
 
 	return {
+		sanitized,
 		source: src.join("\n")
 	};
 };


Test patch:
diff --git a/test/rules/array.spec.js b/test/rules/array.spec.js
index 10ed7e2..93be05d 100644
--- a/test/rules/array.spec.js
+++ b/test/rules/array.spec.js
@@ -191,5 +191,45 @@ describe("Test rule: array", () => {
 			expect(errors[0].type).toBe("evenNumber");
 			expect(o.a).toEqual([2, 4, 8]);
 		});
+
+		describe("conversion behavior", () => {
+			// !! Don't use a $$root schema because the value to check will not be passed as reference but as value
+			const check = v.compile({ data: { type: "array", items: "string", convert: true } });
+			// Single value check
+			it ("should wrap single value into array", () => {
+				const value = { data: "John" };
+				expect(check(value)).toEqual(true);
+				expect(value.data).toEqual(["John"]);
+			});
+			// Already array, one element
+			it ("should not change array with one element", () => {
+				const value = { data: ["John"] };
+				expect(check(value)).toEqual(true);
+				expect(value.data).toEqual(["John"]);
+			});
+			// Already array, multiple elements
+			it ("should not change array with multiple elements", () => {
+				const value = { data: ["John", "Jane"] };
+				expect(check(value)).toEqual(true);
+				expect(value.data).toEqual(["John", "Jane"]);
+			});
+			// Empty array
+			it ("should not change empty array", () => {
+				const value = { data: [] };
+				expect(check(value)).toEqual(true);
+				expect(value.data).toEqual([]);
+			});
+			// Null/undefined
+			it ("should not convert into array if null or undefined", () => {
+				// Null check
+				const value = { data: null };
+				expect(check(value)).toEqual([{ type: "required", field: "data", actual: null, message: "The 'data' field is required." }]);
+				expect(value.data).toEqual(null);
+				// Undefined check
+				const value2 = { data: undefined };
+				expect(check(value2)).toEqual([{ type: "required", field: "data", actual: undefined, message: "The 'data' field is required." }]);
+				expect(value2.data).toEqual(undefined);
+			});
+		});
 	});
 });
diff --git a/test/typescript/rules/array.spec.ts b/test/typescript/rules/array.spec.ts
index 8a4287a..71725ab 100644
--- a/test/typescript/rules/array.spec.ts
+++ b/test/typescript/rules/array.spec.ts
@@ -129,5 +129,44 @@ describe('TypeScript Definitions', () => {
 			});
 		});
 
+		describe("conversion behavior", () => {
+			// !! Don't use a $$root schema because the value to check will not be passed as reference but as value
+			const check = v.compile({ data: { type: "array", items: "string", convert: true } });
+			// Single value check
+			it ("should wrap single value into array", () => {
+				const value = { data: "John" };
+				expect(check(value)).toEqual(true);
+				expect(value.data).toEqual(["John"]);
+			});
+			// Already array, one element
+			it ("should not change array with one element", () => {
+				const value = { data: ["John"] };
+				expect(check(value)).toEqual(true);
+				expect(value.data).toEqual(["John"]);
+			});
+			// Already array, multiple elements
+			it ("should not change array with multiple elements", () => {
+				const value = { data: ["John", "Jane"] };
+				expect(check(value)).toEqual(true);
+				expect(value.data).toEqual(["John", "Jane"]);
+			});
+			// Empty array
+			it ("should not change empty array", () => {
+				const value = { data: [] };
+				expect(check(value)).toEqual(true);
+				expect(value.data).toEqual([]);
+			});
+			// Null/undefined
+			it ("should not convert into array if null or undefined", () => {
+				// Null check
+				const value = { data: null };
+				expect(check(value)).toEqual([{ type: "required", field: "data", actual: null, message: "The 'data' field is required." }]);
+				expect(value.data).toEqual(null);
+				// Undefined check
+				const value2 = { data: undefined };
+				expect(check(value2)).toEqual([{ type: "required", field: "data", actual: undefined, message: "The 'data' field is required." }]);
+				expect(value2.data).toEqual(undefined);
+			});
+		});
 	});
 });