Repository: dynamoosejs/dynamoose
Pull request #748 (2020-03-21 19:35:15)

Children Required Check
This PR changes the behavior of children attributes required check to only check if the parent value exists.


Patch:
diff --git a/docs/api/Schema.md b/docs/api/Schema.md
index 1a289600..18ef52a2 100644
--- a/docs/api/Schema.md
+++ b/docs/api/Schema.md
@@ -242,7 +242,9 @@ You can set a validation on an attribute to ensure the value passes a given vali
 
 ### required: boolean
 
-You can set an attribute to be required when saving documents to DynamoDB. By default this setting is false.
+You can set an attribute to be required when saving documents to DynamoDB. By default this setting is `false`.
+
+In the event the parent object is undefined and `required` is set to `false` on that parent attribute, the required check will not be run on child attributes.
 
 ```js
 {
@@ -253,6 +255,21 @@ You can set an attribute to be required when saving documents to DynamoDB. By de
 }
 ```
 
+```js
+{
+	"data": {
+		"type": Object,
+		"schema": {
+			"name": {
+				"type": String,
+				"required": true // Required will only be checked if `data` exists and is not undefined
+			}
+		}
+		"required": false
+	}
+}
+```
+
 ### enum: array
 
 You can set an attribute to have an enum array, which means it must match one of the values specified in the enum array. By default this setting is undefined and not set to anything.
diff --git a/lib/Document.js b/lib/Document.js
index 5b5877c0..556eb60f 100644
--- a/lib/Document.js
+++ b/lib/Document.js
@@ -189,8 +189,22 @@ function DocumentCarrier(model) {
 		}
 		if (settings.required) {
 			await Promise.all(Document.attributesWithSchema(returnObject).map(async (key) => {
-				const value = utils.object.get(returnObject, key);
-				await model.schema.requiredCheck(key, value);
+				async function check() {
+					const value = utils.object.get(returnObject, key);
+					await model.schema.requiredCheck(key, value);
+				}
+
+				const keyParts = key.split(".");
+				const parentKey = keyParts.slice(0, -1).join(".");
+				if (parentKey) {
+					const parentValue = utils.object.get(returnObject, parentKey);
+					const isParentValueUndefined = typeof parentValue === "undefined" || parentValue === null;
+					if (!isParentValueUndefined) {
+						await check();
+					}
+				} else {
+					await check();
+				}
 			}));
 		}
 		if (settings.enum) {


Test patch:
diff --git a/test/Document.js b/test/Document.js
index 00145cd5..475b054f 100644
--- a/test/Document.js
+++ b/test/Document.js
@@ -1430,6 +1430,36 @@ describe("Document", () => {
 				"error": new Error.ValidationError("age is a required property but has no value when trying to save document"),
 				"schema": {"id": {"type": String}, "age": {"type": Number, "enum": [10, 20], "required": true}}
 			},
+			{
+				"input": [{"id": "test"}, {"required": true}],
+				"output": {"id": "test"},
+				"schema": {"id": {"type": String}, "data": {"type": Object, "schema": {"name": {"type": String, "required": true}}, "required": false}}
+			},
+			{
+				"input": [{"id": "test"}, {"required": true}],
+				"output": {"id": "test"},
+				"schema": {"id": {"type": String}, "data": {"type": Object, "schema": {"name": {"type": String, "required": false}}, "required": false}}
+			},
+			{
+				"input": [{"id": "test"}, {"required": true}],
+				"error": new Error.ValidationError("data is a required property but has no value when trying to save document"),
+				"schema": {"id": {"type": String}, "data": {"type": Object, "schema": {"name": {"type": String, "required": false}}, "required": true}}
+			},
+			{
+				"input": [{"id": "test", "data": {}}, {"required": true}],
+				"output": {"id": "test", "data": {}},
+				"schema": {"id": {"type": String}, "data": {"type": Object, "schema": {"name": {"type": String, "required": false}}, "required": true}}
+			},
+			{
+				"input": [{"id": "test", "data": {"email": "test@test.com"}}, {"required": true}],
+				"error": new Error.ValidationError("data.name is a required property but has no value when trying to save document"),
+				"schema": {"id": {"type": String}, "data": {"type": Object, "schema": {"email": String, "name": {"type": String, "required": true}}}}
+			},
+			{
+				"input": [{"id": "test"}, {"required": true}],
+				"error": new Error.ValidationError("data is a required property but has no value when trying to save document"),
+				"schema": {"id": {"type": String}, "data": {"type": Object, "schema": {"name": {"type": String, "required": true}}, "required": true}}
+			},
 			{
 				"input": [{"id": 1, "ttl": 1}, {"type": "fromDynamo", "checkExpiredItem": true}],
 				"model": ["User", {"id": Number}, {"create": false, "waitForActive": false, "expires": 1000}],