Avoid saving null attributes of nested objects - Amazon SDK for Java 2.x
Services or capabilities described in Amazon Web Services documentation might vary by Region. To see the differences applicable to the China Regions, see Getting Started with Amazon Web Services in China (PDF).

Avoid saving null attributes of nested objects

You can skip null attributes of nested objects when saving a data class object to DynamoDB by applying the @DynamoDbIgnoreNulls annotation. By contrast, top-level attributes with null values are never saved to the database.

To illustrate how the annotation works, the code example uses the following two beans.

The following data class contains two InnerBean fields. The getter method, getInnerBeanWithoutAnno(), is not annotated. The getInnerBeanWithIgnoreNullsAnno() method is annotated with @DynamoDbIgnoreNulls.

@DynamoDbBean public class MyBean { private String id; private String name; private InnerBean innerBeanWithoutAnno; private InnerBean innerBeanWithIgnoreNullsAnno; @DynamoDbPartitionKey public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public InnerBean getInnerBeanWithoutAnno() { return innerBeanWithoutAnno; } public void setInnerBeanWithoutAnno(InnerBean innerBeanWithoutAnno) { this.innerBeanWithoutAnno = innerBeanWithoutAnno; } @DynamoDbIgnoreNulls public InnerBean getInnerBeanWithIgnoreNullsAnno() { return innerBeanWithIgnoreNullsAnno; } public void setInnerBeanWithIgnoreNullsAnno(InnerBean innerBeanWithAnno) { this.innerBeanWithIgnoreNullsAnno = innerBeanWithAnno; } @Override public String toString() { return new StringJoiner(", ", MyBean.class.getSimpleName() + "[", "]") .add("innerBeanWithoutAnno=" + innerBeanWithoutAnno) .add("innerBeanWithIgnoreNullsAnno=" + innerBeanWithIgnoreNullsAnno) .add("id='" + id + "'") .add("name='" + name + "'") .toString(); } }

Instances of the following InnerBean class are fields of MyBean and are used in the following example code.

@DynamoDbBean public class InnerBean { private String innerBeanFieldString; private Integer innerBeanFieldInteger; public String getInnerBeanFieldString() { return innerBeanFieldString; } public void setInnerBeanFieldString(String innerBeanFieldString) { this.innerBeanFieldString = innerBeanFieldString; } public Integer getInnerBeanFieldInteger() { return innerBeanFieldInteger; } public void setInnerBeanFieldInteger(Integer innerBeanFieldInteger) { this.innerBeanFieldInteger = innerBeanFieldInteger; } @Override public String toString() { return new StringJoiner(", ", InnerBean.class.getSimpleName() + "[", "]") .add("innerBeanFieldString='" + innerBeanFieldString + "'") .add("innerBeanFieldInteger=" + innerBeanFieldInteger) .toString(); } }

The following code example creates an InnerBean object and sets only one of its two attributes with a value.

public void ignoreNullsAnnoUsingPutItemExample(DynamoDbTable<MyBean> myBeanTable) { // Create an InnerBean object and give only one attribute a value. InnerBean innerBeanOneAttributeSet = new InnerBean(); innerBeanOneAttributeSet.setInnerBeanFieldInteger(200); // Create a MyBean instance and use the same InnerBean instance both for attributes. MyBean bean = new MyBean(); bean.setId("1"); bean.setInnerBeanWithoutAnno(innerBeanOneAttributeSet); bean.setInnerBeanWithIgnoreNullsAnno(innerBeanOneAttributeSet); Map<String, AttributeValue> itemMap = myBeanTable.tableSchema().itemToMap(bean, true); logger.info(itemMap.toString()); // Log the map that is sent to the database. // {innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)}), id=AttributeValue(S=1), innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})} // Save the MyBean object to the table. myBeanTable.putItem(bean); }

To visualize the low-level data that is sent to DynamoDB, the code logs the attribute map before saving the MyBean object.

The logged output shows that the innerBeanWithIgnoreNullsAnno outputs one attribute,

innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)})

The innerBeanWithoutAnno instance outputs two attributes. One attribute has a value of 200 and the other is a null-valued attribute.

innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})

The following JSON representation makes it easier to see the data that is saved to DynamoDB.

{ "id": { "S": "1" }, "innerBeanWithIgnoreNullsAnno": { "M": { "innerBeanFieldInteger": { "N": "200" } } }, "innerBeanWithoutAnno": { "M": { "innerBeanFieldInteger": { "N": "200" }, "innerBeanFieldString": { "NULL": true } } } }

You can use the following StaticTableSchema version of the table schemas in place data class annotations.

public static TableSchema<MyBean> buildStaticSchemas() { StaticTableSchema<InnerBean> innerBeanStaticTableSchema = StaticTableSchema.builder(InnerBean.class) .newItemSupplier(InnerBean::new) .addAttribute(String.class, a -> a.name("innerBeanFieldString") .getter(InnerBean::getInnerBeanFieldString) .setter(InnerBean::setInnerBeanFieldString)) .addAttribute(Integer.class, a -> a.name("innerBeanFieldInteger") .getter(InnerBean::getInnerBeanFieldInteger) .setter(InnerBean::setInnerBeanFieldInteger)) .build(); return StaticTableSchema.builder(MyBean.class) .newItemSupplier(MyBean::new) .addAttribute(String.class, a -> a.name("id") .getter(MyBean::getId) .setter(MyBean::setId) .addTag(primaryPartitionKey())) .addAttribute(String.class, a -> a.name("name") .getter(MyBean::getName) .setter(MyBean::setName)) .addAttribute(EnhancedType.documentOf(InnerBean.class, innerBeanStaticTableSchema), a -> a.name("innerBeanWithoutAnno") .getter(MyBean::getInnerBeanWithoutAnno) .setter(MyBean::setInnerBeanWithoutAnno)) .addAttribute(EnhancedType.documentOf(InnerBean.class, innerBeanStaticTableSchema, b -> b.ignoreNulls(true)), a -> a.name("innerBeanWithIgnoreNullsAnno") .getter(MyBean::getInnerBeanWithIgnoreNullsAnno) .setter(MyBean::setInnerBeanWithIgnoreNullsAnno)) .build(); }