Repository: icebob/fastest-validator
Pull request #143 (2020-05-26 18:21:25)

Shorthand for nested objects
With this shorthand, validating a nested object will be easier.
***old:***
```js
schema = {
   a: {
      type: "object",
      props: {
         b: {
            type: "object",
            props: {
               c: "string"
            }
         }
      }
   }
}
```

***new:*** 
```js
schema = {
   a: {
      $$type: "object",
      b: {
         $$type: "object",
         c: "string"
      }
   }
}
```


Patch:
diff --git a/README.md b/README.md
index cd7340b..47900f7 100644
--- a/README.md
+++ b/README.md
@@ -320,6 +320,27 @@ const schema = {
 }
 ```
 
+### Nested objects
+
+```js
+const schema = {
+   dot: {
+      $$type: "object",
+      x: "number",  // object props here
+      y: "number",  // object props here
+   }, 
+   circle: {
+      $$type: "object|optional", // using other shorthands
+      o: {
+         $$type: "object",
+         x: "number",
+         y: "number",
+      },
+      r: "number"
+   }
+};
+```
+
 # Alias definition
 You can define custom aliases.
 
diff --git a/index.d.ts b/index.d.ts
index b7e0d9e..3696e58 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -918,7 +918,7 @@ declare module "fastest-validator" {
 		 * @return {ValidationRule}
 		 */
 		getRuleFromSchema(
-			name: ValidationRuleName | ValidationRuleName[]
+			name: ValidationRuleName | ValidationRuleName[] | { [key: string]: unknown }
 		): {
 			messages: MessagesType;
 			schema: ValidationSchema;
diff --git a/lib/validator.js b/lib/validator.js
index 6bf8049..5d31de4 100644
--- a/lib/validator.js
+++ b/lib/validator.js
@@ -296,6 +296,20 @@ class Validator {
 				schema.optional = true;
 		}
 
+		if (schema.$$type) {
+			const type = schema.$$type;
+			const otherShorthandProps = this.getRuleFromSchema(type).schema;
+			delete schema.$$type;
+			const props = Object.assign({}, schema);
+
+			for (const key in schema) {  // clear object without changing reference
+				delete schema[key];
+			}
+
+			deepExtend(schema, otherShorthandProps, { skipIfExist: true });
+			schema.props = props;
+		}
+
 		const alias = this.aliases[schema.type];
 		if (alias) {
 			delete schema.type;


Test patch:
diff --git a/test/typescript/validator.spec.ts b/test/typescript/validator.spec.ts
index 3d0a8c2..44ea3db 100644
--- a/test/typescript/validator.spec.ts
+++ b/test/typescript/validator.spec.ts
@@ -204,6 +204,24 @@ describe('TypeScript Definitions', () => {
             });
 
         });
+
+        describe("Test object shorthand rule ($$type)", () => {
+            it("should convert", () => {
+                const res = v.getRuleFromSchema({
+                    $$type: "object",
+                    name: { type: "string" },
+                    age: { type: "number" }
+                });
+        
+                expect(res.schema).toEqual({ 
+                    type: "object" ,
+                    props: {
+                        name: { type: "string" },
+                        age: { type: "number" }
+                    } 
+                });
+            });
+        });
     });
 
     describe('Test makeError', () => {
diff --git a/test/validator.spec.js b/test/validator.spec.js
index e11e660..3623d0f 100644
--- a/test/validator.spec.js
+++ b/test/validator.spec.js
@@ -184,7 +184,7 @@ describe("Test getRuleFromSchema method", () => {
 		}).toThrowError("Invalid 's' type in validator schema.");
 	});
 
-	describe("Test string shorthard rules", () => {
+	describe("Test string shorthand rules", () => {
 
 		it("should convert only type", () => {
 			const res = v.getRuleFromSchema("string");
@@ -202,6 +202,41 @@ describe("Test getRuleFromSchema method", () => {
 		});
 
 	});
+
+	describe("Test objects shorthand rule ($$type)", () => {
+		it("should convert", () => {
+			const res = v.getRuleFromSchema({
+				$$type: "object",
+				name: { type: "string" },
+				age: { type: "number" }
+			});
+	
+			expect(res.schema).toEqual({ 
+				type: "object" ,
+				props: {
+					name: { type: "string" },
+					age: { type: "number" }
+				} 
+			});
+		});
+
+		it("should work with other shorthand rules", () => {
+			const res = v.getRuleFromSchema({
+				$$type: "object|optional",
+				name: { type: "string" },
+				age: { type: "number" }
+			});
+	
+			expect(res.schema).toEqual({ 
+				type: "object" ,
+				optional: true,
+				props: {
+					name: { type: "string" },
+					age: { type: "number" }
+				} 
+			});
+		});
+	});
 });
 
 describe("Test makeError", () => {
@@ -496,3 +531,47 @@ describe("Test default settings", () => {
 		});
 	});
 });
+
+describe("Test objects shorthand", () => {
+	const v = new Validator();
+
+	it("should work with nested objects", () => {
+		const check = v.compile({
+			dot: {
+				$$type: "object",
+				x: "number",
+				y: "number",
+			}, 
+			circle: {
+				$$type: "object",
+				o: {
+					$$type: "object",
+					x: "number",
+					y: "number",
+				},
+				r: "number"
+			}
+		});
+
+		expect(
+			check({ 
+				dot: { x: 10, y: 3 }, 
+				circle: {
+					o: { x: 10, y: 3 }, 
+					r: 30
+				} 
+			})
+		).toBe(true);
+
+		expect(
+			check({ 
+				dot: { x: 10, y: 3 }, 
+				circle: {
+					o: { x: 10, y: "s" }, 
+					r: 30
+				} 
+			})
+		).toEqual(expect.any(Array));
+	});
+});
+