Repository: dynamoosejs/dynamoose (js)

Issue:
Creating a model with undefined attributes throws an error
```
const User = new Schema({
   age: Number,
   id: {
       type: String,
       hashKey: true,
   },
   name: String,
})
```

```
User.create({
   age: undefined,
   id: 1,
   name: "Tim",
});
```

Note: The undefined attribute has to be the first attribute of the object.

Error: 
> Cannot convert undefined or null to object

Stacktrace: 
> at Function.keys (<anonymous>) at isValid (/Users/mick/git/myproject/node_modules/dynamoose/lib/Document.js:46:24)

Pull request description:
First undefined value property error
This fixes a bug where:

```
Model.create({"name": undefined, "id": 1}); // Would throw error
Model.create({"id": 1, "name": undefined}); // Would succeed
```

This is due to the `isDynamoObject` method not handling `undefined` or `null` correctly. Due to the fact that it bails early if the value is not a Dynamo value, the second option would work fine.

Closes #815 

Patch:
diff --git a/lib/Document.js b/lib/Document.js
index 04493f0d..4c4de3ee 100644
--- a/lib/Document.js
+++ b/lib/Document.js
@@ -10,6 +10,9 @@ const staticMethods = {
 	"isDynamoObject": function (object, recurrsive = false) {
 		// This function will check to see if a nested object is valid by calling Document.isDynamoObject recursively
 		const isValid = (value) => {
+			if (typeof value === "undefined" || value === null) {
+				return false;
+			}
 			const keys = Object.keys(value);
 			const key = keys[0];
 			const nestedResult = (typeof value[key] === "object" && !(value[key] instanceof Buffer) ? (Array.isArray(value[key]) ? value[key].every((value) => this.isDynamoObject(value, true)) : this.isDynamoObject(value[key])) : true);


Test patch:
diff --git a/test/Document.js b/test/Document.js
index 8288cc55..e6d97d02 100644
--- a/test/Document.js
+++ b/test/Document.js
@@ -182,6 +182,28 @@ describe("Document", () => {
 					});
 				});
 
+				it("Should save with correct object with undefined as value without required or default as first property", async () => {
+					putItemFunction = () => Promise.resolve();
+					User = new Model("User", new Schema({"id": Number, "name": String}));
+					user = new User({"name": undefined, "id": 1});
+					await callType.func(user).bind(user)();
+					expect(putParams).to.eql([{
+						"Item": {"id": {"N": "1"}},
+						"TableName": "User"
+					}]);
+				});
+
+				it("Should save with correct object with undefined as value without required or default as second property", async () => {
+					putItemFunction = () => Promise.resolve();
+					User = new Model("User", new Schema({"id": Number, "name": String}));
+					user = new User({"id": 1, "name": undefined});
+					await callType.func(user).bind(user)();
+					expect(putParams).to.eql([{
+						"Item": {"id": {"N": "1"}},
+						"TableName": "User"
+					}]);
+				});
+
 				it("Should save with correct object with string set", async () => {
 					putItemFunction = () => Promise.resolve();
 					User = new Model("User", {"id": Number, "friends": [String]});
@@ -1283,6 +1305,14 @@ describe("Document", () => {
 			{
 				"input": {"data": {"L": []}},
 				"output": true
+			},
+			{
+				"input": {"prop": undefined},
+				"output": false
+			},
+			{
+				"input": {"prop": null},
+				"output": false
 			}
 		];
 
diff --git a/test/Model.js b/test/Model.js
index 22b5938a..27e59f60 100644
--- a/test/Model.js
+++ b/test/Model.js
@@ -1349,6 +1349,42 @@ describe("Model", () => {
 					});
 				});
 
+				it("Should send correct params to putItem with value as undefined as first property", async () => {
+					createItemFunction = () => Promise.resolve();
+					await callType.func(User).bind(User)({"name": undefined, "id": 1});
+					expect(createItemParams).to.be.an("object");
+					expect(createItemParams).to.eql({
+						"ConditionExpression": "attribute_not_exists(#__hash_key)",
+						"ExpressionAttributeNames": {
+							"#__hash_key": "id"
+						},
+						"Item": {
+							"id": {
+								"N": "1"
+							}
+						},
+						"TableName": "User"
+					});
+				});
+
+				it("Should send correct params to putItem with value as undefined as second property", async () => {
+					createItemFunction = () => Promise.resolve();
+					await callType.func(User).bind(User)({"id": 1, "name": undefined});
+					expect(createItemParams).to.be.an("object");
+					expect(createItemParams).to.eql({
+						"ConditionExpression": "attribute_not_exists(#__hash_key)",
+						"ExpressionAttributeNames": {
+							"#__hash_key": "id"
+						},
+						"Item": {
+							"id": {
+								"N": "1"
+							}
+						},
+						"TableName": "User"
+					});
+				});
+
 				it("Should not include attributes that do not exist in schema", async () => {
 					createItemFunction = () => Promise.resolve();
 					await callType.func(User).bind(User)({"id": 1, "name": "Charlie", "hello": "world"});