16

The new AWS DynamoDB document API allows 2 new data types that correspond directly to the underlying JSON representation: Map (aka JSON object) and List (aka JSON array).

However, I can't find a way to update attributes of these data types without completely overwriting them. In contrast, a Number attribute can be updated by ADDing another number, so in Java you can do something like:

new AttributeUpdate("Some numeric attribute").addNumeric(17);

Similarly you can addElements to an attribute of a Set data type. (In the old API you would use AttributeAction.ADD for both purposes.)

But for a Map or a List, it seems you must update the previous value locally, then PUT it instead of that value, for example in Java:

List<String> list = item.getList("Some list attribute");
list.add("new element");
new AttributeUpdate("Some list attribute").put(list);

This is much less readable, and under some circumstances much less efficient.

So my questions are:

  1. Is there a way to update an attribute of a Map or a List data type without overwriting the previous value? For example, to add an element to a List, or to put an element in a Map?

  2. How would you implement it using the Java API?

  3. Do you know of plans to support this in the future?

1

3 Answers 3

13

Please take a look at UpdateExpression in the UpdateItem API

For example given an item with a list:

{
    "hashkey": {"S" : "my_key"},
    "my_list" : {"L": 
        [{"N":"3"},{"N":"7"} ]
}

You can update the list with code like the following:

UpdateItemRequest request = new UpdateItemRequest();
request.setTableName("myTableName");
request.setKey(Collections.singletonMap("hashkey", 
    new AttributeValue().withS("my_key")));
request.setUpdateExpression("list_append(:prepend_value, my_list)");
request.setExpressionAttributeValues(
    Collections.singletonMap(":prepend_value", 
        new AttributeValue().withN("1"))
    );
dynamodb.updateItem(request);`

You can also append to the list by reversing the order of the arguments in the list_append expression.

An expression like: SET user.address.zipcode = :zip would address a JSON map element combined with expression attribute values {":zip" : {"N":"12345"}}

Sign up to request clarification or add additional context in comments.

2 Comments

Any ideas if the value I was appending was itself a Map instead of a number as in above example
UpdateItemRequest has been replaced with UpdateItemSpec
6

Base on DynamoDB examples, this also work (scala)

val updateItemSpec:UpdateItemSpec = new UpdateItemSpec()
    .withPrimaryKey("hashkey", my_key)
  .withUpdateExpression("set my_list = list_append(:prepend_value, my_list)")
  .withValueMap(new ValueMap()
      .withList(":prepend_value", "1"))
  .withReturnValues(ReturnValue.UPDATED_NEW)
println("Updating the item...")
val outcome: UpdateItemOutcome = table.updateItem(updateItemSpec)
println("UpdateItem succeeded:\n" + outcome.getItem.toJSONPretty)

3 Comments

Please elaborate on how this code answers the question (this answer was in the Late Answers review queue).
Base on the answer Ben Schwartz i Just wat to provide another way to resolved in the same way that the examples by amazon are performed
this quite does nt work Can you help with stackoverflow.com/questions/53184660/…
0

A generic function to add or update a key/value pairs. attribute updateColumn should be of type map.

Update tableName attribute name should be passed as attributeName under key:value pairs where primaryKey = primaryKeyValue

public boolean insertKeyValue(String tableName, String primaryKey, String 
    primaryKeyValue, String attributeName, String newKey, String newValue) {

    //Configuration to connect to DynamoDB
    Table table = dynamoDB.getTable(tableName);
    boolean insertAppendStatus = false;
    try {
        //Updates when map is already exist in the table
        UpdateItemSpec updateItemSpec = new UpdateItemSpec()
            .withPrimaryKey(primaryKey, primaryKeyValue)
            .withReturnValues(ReturnValue.ALL_NEW)
            .withUpdateExpression("set #columnName." + newKey + " = :columnValue")
            .withNameMap(new NameMap().with("#columnName", attributeName))
            .withValueMap(new ValueMap().with(":columnValue", newValue))
            .withConditionExpression("attribute_exists("+ attributeName +")");

        table.updateItem(updateItemSpec);
        insertAppendStatus = true;
    //Add map column when it's not exist in the table
    } catch (ConditionalCheckFailedException e) {
        HashMap<String, String> map =  new HashMap<>();
        map.put(newKey, newValue);
        UpdateItemSpec updateItemSpec = new UpdateItemSpec()
            .withPrimaryKey(primaryKey,primaryKeyValue)
            .withReturnValues(ReturnValue.ALL_NEW)
            .withUpdateExpression("set #columnName = :m")
            .withNameMap(new NameMap().with("#columnName", attributeName))
            .withValueMap(new ValueMap().withMap(":m", map));

        table.updateItem(updateItemSpec);
        insertAppendStatus = true;
    } catch(Exception e) {
        e.printStackTrace();
    }
    return insertAppendStatus;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.