Repository: jeremydaly/dynamodb-toolbox
Pull request #146 (2021-03-18 10:25:49)

fix undefined errors with $add, $remove, etc
When a `number`, `set`, `list` or `map` attribute value is `undefined`, an error is thrown:

`TypeError: Cannot read property '$add' of undefined`

This PR fixes this issue. 



Patch:
diff --git a/src/classes/Entity.ts b/src/classes/Entity.ts
index a986a416..3c512246 100644
--- a/src/classes/Entity.ts
+++ b/src/classes/Entity.ts
@@ -801,19 +801,19 @@ class Entity<
         && (!mapping.link || (mapping.link && mapping.save === true))
       ) {
         // If a number or a set and adding
-        if (['number','set'].includes(mapping.type) && (data[field].$add !== undefined && data[field].$add !== null)) {
+        if (['number','set'].includes(mapping.type) && (data[field]?.$add !== undefined && data[field]?.$add !== null)) {
           ADD.push(`#${field} :${field}`)
           values[`:${field}`] = validateType(mapping,field,data[field].$add)
           // Add field to names
           names[`#${field}`] = field
         // if a set and deleting items
-        } else if (mapping.type === 'set' && data[field].$delete) {
+        } else if (mapping.type === 'set' && data[field]?.$delete) {
           DELETE.push(`#${field} :${field}`)
           values[`:${field}`] = validateType(mapping,field,data[field].$delete)
           // Add field to names
           names[`#${field}`] = field
         // if a list and removing items by index
-        } else if (mapping.type === 'list' && Array.isArray(data[field].$remove)) {
+        } else if (mapping.type === 'list' && Array.isArray(data[field]?.$remove)) {
           data[field].$remove.forEach((i:number) => {
             if (typeof i !== 'number') error(`Remove array for '${field}' must only contain numeric indexes`)
             REMOVE.push(`#${field}[${i}]`)
@@ -821,7 +821,7 @@ class Entity<
           // Add field to names
           names[`#${field}`] = field
         // if list and appending or prepending
-        } else if (mapping.type === 'list' && (data[field].$append || data[field].$prepend)) {
+        } else if (mapping.type === 'list' && (data[field]?.$append || data[field]?.$prepend)) {
           if (data[field].$append) {
             SET.push(`#${field} = list_append(#${field},:${field})`)
             values[`:${field}`] = validateType(mapping,field,data[field].$append)
@@ -841,7 +841,7 @@ class Entity<
           // Add field to names
           names[`#${field}`] = field
         // if a map and updating by nested attribute/index
-        } else if (mapping.type === 'map' && data[field].$set) {
+        } else if (mapping.type === 'map' && data[field]?.$set) {
           Object.keys(data[field].$set).forEach(f => {
 
             // TODO: handle null values to remove


Test patch:
diff --git a/src/__tests__/entity.update.unit.test.ts b/src/__tests__/entity.update.unit.test.ts
index df91df94..8d59ba67 100644
--- a/src/__tests__/entity.update.unit.test.ts
+++ b/src/__tests__/entity.update.unit.test.ts
@@ -312,6 +312,26 @@ describe('update',()=>{
     expect(Key).toEqual({ pk: 'test-pk', sk: 'test-sk' })
     expect(TableName).toBe('test-table')
   })
+  
+  it('ignores fields with no value', () => {
+    let { ExpressionAttributeValues } = TestEntity.updateParams({
+      pk: 'test-pk',
+      sk: 'test-pk',
+      test_string: undefined,
+      test_number: undefined,
+      test_number_set: undefined,
+      test_string_set: undefined,
+      test_list: undefined,
+      test_map: undefined,
+    })
+    
+    expect(ExpressionAttributeValues).not.toHaveProperty(':test_string')
+    expect(ExpressionAttributeValues).not.toHaveProperty(':test_number')
+    expect(ExpressionAttributeValues).not.toHaveProperty(':test_number_set')
+    expect(ExpressionAttributeValues).not.toHaveProperty(':test_string_set')
+    expect(ExpressionAttributeValues).not.toHaveProperty(':test_list')
+    expect(ExpressionAttributeValues).not.toHaveProperty(':test_map')
+  })
 
 
   it('performs a delete operation', () => {