amazon-web-servicesamazon-dynamodbaws-sdkaws-sdk-jsdocumentclient

UpdateExpression: Add other attribute's value to list


Given the following DynamoDB document:

{
    "myobject" : {"foo" : "bar"},
    "mylist" : [{"some" : "stuff}]
}

My goal is to update this document to get the following result:

{
    "myobject" : {"foo" : "bar"},
    "mylist" : [{"some" : "stuff}, {"foo" : "bar"}]
}

My request's params look like this:

let params = {
    TableName: doctorSlotsTable,
    Key: {
      hashKey: hash,
      rangeKey: range
    },
    UpdateExpression: 'SET mylist = list_append(if_not_exists(mylist, :empty_list), [myobject])',
    ExpressionAttributeValues : {
      ':empty_list' : []
    },
    ReturnValues : "UPDATED_NEW"
  };

This obviously does not work because the [ in the list_append triggers a syntax error.
Is there any solution to achieve that without having to get the data in a previous request and add it manually to the list ?


Solution

  • Unfortunately you cannot use an attribute name as an operand to list_append(...) unless that attribute is itself a list. The best you can do I believe would be to store myobject in the proper type up front, and then update it as expected.

    Since storage is cheap & network/compute are expensive here, you could even duplicate the data to have one of them in the right form.

    Here's a full example, where createTable() and deleteTable() do exactly what you think:

    const PK = 'the item';
    async function createObjAndList() {
        const docClient = new DocumentClient();
    
        const myObject = { foo: "bar" };
        const theItem = {
            PK,
            myObject,
            myObjectAsList: [ myObject ],
            myList: [ { some : "stuff" } ],
        };
        const putParams = {
            TableName,
            Item: theItem
        }
        await docClient.put(putParams).promise();
        console.log(`Put item ${util.inspect(theItem)}`);
    }
    
    async function updateListWithObject() {
        const docClient = new DocumentClient();
    
        const updateParams = {
            TableName,
            Key: { PK },
            UpdateExpression: `SET #myList = list_append(if_not_exists(#myList, :emptyList), #myObjectAsList)`,
            ExpressionAttributeNames: {
                '#myList': 'myList',
                '#myObjectAsList': 'myObjectAsList',
            },
            ExpressionAttributeValues: {
                ':emptyList': [],
            }
        }
        await docClient.update(updateParams).promise();
        console.log(`Updated list to include object`);
    }
    
    async function getObjAndList() {
        const docClient = new DocumentClient();
    
        const results = await docClient.get({ TableName, Key: { PK }}).promise();
        console.log(`Item is now: ${util.inspect(results.Item)}`);
    }
    
    if (module === require.main) {
        (async () => {
            try {
                await createTable();
                await createObjAndList()
                await updateListWithObject();
                await getObjAndList();
            } catch (err) {
                console.log(`Error: ${err.message}`);
            } finally {
                await deleteTable();
            }
        })();
    }
    

    The output from this is:

    Put item {
      PK: 'the item',
      myObject: { foo: 'bar' },
      myObjectAsList: [ { foo: 'bar' } ],
      myList: [ { some: 'stuff' } ]
    }
    Updated list to include object
    Item is now: {
      myList: [ { some: 'stuff' }, { foo: 'bar' } ],
      myObject: { foo: 'bar' },
      PK: 'the item',
      myObjectAsList: [ { foo: 'bar' } ]
    }