diff --git a/docs/design/services/dynamodb/high-level-library/DocumentAPI.md b/docs/design/services/dynamodb/high-level-library/DocumentAPI.md
index 9abea94d3c2f..7c54535da3f0 100644
--- a/docs/design/services/dynamodb/high-level-library/DocumentAPI.md
+++ b/docs/design/services/dynamodb/high-level-library/DocumentAPI.md
@@ -44,7 +44,7 @@ providers.
// New API in TableSchema to create a DocumentTableSchema
DocumentTableSchema documentTableSchema =
- TableSchema.fromDocumentSchemaBuilder()
+ TableSchema.documentSchemaBuilder()
.addIndexPartitionKey(primaryIndexName(), "sample_hash_name", AttributeValueType.S)
.addIndexSortKey("gsi_index", "sample_sort_name", AttributeValueType.N)
.addAttributeConverterProviders(cutomAttributeConverters)
@@ -78,7 +78,7 @@ EnhancedDocument documentTableItem = documentTable.getItem(
Number sampleSortvalue = documentTableItem.get("sample_sort_name", EnhancedType.of(Number.class));
// Accessing an attribute from document using specific getters.
-sampleSortvalue = documentTableItem.getSdkNumber("sample_sort_name");
+sampleSortvalue = documentTableItem.getNumber("sample_sort_name");
// Accessing an attribute of custom class using custom converters.
CustomClass customClass = documentTableItem.get("custom_nested_map", new CustomAttributeConverter()));
diff --git a/services-custom/dynamodb-enhanced/pom.xml b/services-custom/dynamodb-enhanced/pom.xml
index 247a33d88a94..9257fb782ae3 100644
--- a/services-custom/dynamodb-enhanced/pom.xml
+++ b/services-custom/dynamodb-enhanced/pom.xml
@@ -106,6 +106,11 @@
aws-core${awsjavasdk.version}
+
+ software.amazon.awssdk
+ json-utils
+ ${awsjavasdk.version}
+ software.amazon.awssdkhttp-client-spi
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DefaultAttributeConverterProvider.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DefaultAttributeConverterProvider.java
index 28fb0ab8d240..45db89f5283c 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DefaultAttributeConverterProvider.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DefaultAttributeConverterProvider.java
@@ -61,6 +61,7 @@
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.OptionalLongAttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.PeriodAttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.SdkBytesAttributeConverter;
+import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.SdkNumberAttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.SetAttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.ShortAttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.StringAttributeConverter;
@@ -88,7 +89,7 @@
@ThreadSafe
@Immutable
public final class DefaultAttributeConverterProvider implements AttributeConverterProvider {
- private static DefaultAttributeConverterProvider INSTANCE = getDefaultBuilder().build();
+ private static final DefaultAttributeConverterProvider INSTANCE = getDefaultBuilder().build();
private static final Logger log = Logger.loggerFor(DefaultAttributeConverterProvider.class);
@@ -247,7 +248,8 @@ private static Builder getDefaultBuilder() {
.addConverter(UuidAttributeConverter.create())
.addConverter(ZonedDateTimeAsStringAttributeConverter.create())
.addConverter(ZoneIdAttributeConverter.create())
- .addConverter(ZoneOffsetAttributeConverter.create());
+ .addConverter(ZoneOffsetAttributeConverter.create())
+ .addConverter(SdkNumberAttributeConverter.create());
}
/**
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/TableSchema.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/TableSchema.java
index c6926f73187d..63e7050b3ee6 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/TableSchema.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/TableSchema.java
@@ -20,7 +20,9 @@
import java.util.Map;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
+import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument;
import software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.DocumentTableSchema;
import software.amazon.awssdk.enhanced.dynamodb.mapper.ImmutableTableSchema;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
@@ -84,6 +86,16 @@ static BeanTableSchema fromBean(Class beanClass) {
return BeanTableSchema.create(beanClass);
}
+ /**
+ * Provides interfaces to interact with DynamoDB tables as {@link EnhancedDocument} where the complete Schema of the table is
+ * not required.
+ *
+ * @return A {@link DocumentTableSchema.Builder} for instantiating DocumentTableSchema.
+ */
+ static DocumentTableSchema.Builder documentSchemaBuilder() {
+ return DocumentTableSchema.builder();
+ }
+
/**
* Scans an immutable class that has been annotated with DynamoDb immutable annotations and then returns a
* {@link ImmutableTableSchema} implementation of this interface that can map records to and from items of that
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.java
index e79c3037f8db..0ade5fb9b495 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.java
@@ -15,76 +15,95 @@
package software.amazon.awssdk.enhanced.dynamodb.document;
+import static software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider.defaultProvider;
+
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import software.amazon.awssdk.annotations.NotThreadSafe;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.core.SdkNumber;
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
-import software.amazon.awssdk.enhanced.dynamodb.DefaultAttributeConverterProvider;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.internal.document.DefaultEnhancedDocument;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.utils.Validate;
+
/**
- * Interface representing Document API for DynamoDB. Document API operations are used to carry open content i.e. data with no
- * fixed schema, data that can't be modeled using rigid types, or data that has a schema. This interface specifies all the
- * methods to access a Document, also provides constructor methods for instantiating Document that can be used to read and write
- * to DynamoDB using EnhancedDynamoDB client.
- *
- * TODO : Add some examples in the Java Doc after API Surface review.
+ * Interface representing the Document API for DynamoDB. The Document API operations are designed to work with open content,
+ * such as data with no fixed schema, data that cannot be modeled using rigid types, or data that has a schema.
+ * This interface provides all the methods required to access a Document, as well as constructor methods for creating a
+ * Document that can be used to read and write to DynamoDB using the EnhancedDynamoDB client.
+ * Additionally, this interface provides flexibility when working with data, as it allows you to work with data that is not
+ * necessarily tied to a specific data model.
+ * The EnhancedDocument interface provides two ways to use AttributeConverterProviders:
+ *
Enhanced Document with default attribute Converter to convert the attribute of DDB item to basic default primitive types in
+ * Java
+ * {@snippet :
+ * EnhancedDocument enhancedDocument = EnhancedDocument.builder().attributeConverterProviders(AttributeConverterProvider
+ * .defaultProvider()).build();
+ *}
+ *
Enhanced Document with Custom attribute Converter to convert the attribute of DDB Item to Custom Type.
+ * {@snippet :
+ * // CustomAttributeConverterProvider.create() is an example for some Custom converter provider
+ * EnhancedDocument enhancedDocumentWithCustomConverter = EnhancedDocument.builder().attributeConverterProviders
+ * (CustomAttributeConverterProvider.create(), AttributeConverterProvide.defaultProvider())
+ * .putWithType("customObject", customObject, EnhancedType.of(CustomClass.class))
+ * .build();
+ *}
+ *
Enhanced Document can be created with Json as input using Static factory method.In this case it used
+ * defaultConverterProviders.
+ * {@snippet :
+ * EnhancedDocument enhancedDocumentWithCustomConverter = EnhancedDocument.fromJson("{\"k\":\"v\"}");
+ *}
+ * The attribute converter are always required to be provided, thus for default conversion
+ * {@link AttributeConverterProvider#defaultProvider()} must be supplied.
*/
@SdkPublicApi
public interface EnhancedDocument {
/**
- * Convenience factory method - instantiates an EnhancedDocument from the given JSON String.
- *
- * @param json The JSON string representation of DynamoDB Item.
+ * Creates a new EnhancedDocument instance from a JSON string.
+ * The {@link AttributeConverterProvider#defaultProvider()} is used as the default ConverterProvider.
+ * To use a custom ConverterProvider, use the builder methods: {@link Builder#json(String)} to supply the JSON string,
+ * then use {@link Builder#attributeConverterProviders(AttributeConverterProvider...)} to provide the custom
+ * ConverterProvider.
+ * {@snippet :
+ * EnhancedDocument documentFromJson = EnhancedDocument.fromJson("{\"key\": \"Value\"}");
+ *}
+ * @param json The JSON string representation of a DynamoDB Item.
* @return A new instance of EnhancedDocument.
+ * @throws IllegalArgumentException if the json parameter is null
*/
static EnhancedDocument fromJson(String json) {
- if (json == null) {
- return null;
- }
+ Validate.paramNotNull(json, "json");
return DefaultEnhancedDocument.builder()
.json(json)
- .attributeConverterProviders(DefaultAttributeConverterProvider.create())
+ .attributeConverterProviders(defaultProvider())
.build();
}
/**
- * Convenience factory method - instantiates an EnhancedDocument from the given AttributeValueMap.
+ * Creates a new EnhancedDocument instance from a AttributeValue Map.
+ * The {@link AttributeConverterProvider#defaultProvider()} is used as the default ConverterProvider.
+ * Example usage:
+ * {@snippet :
+ * EnhancedDocument documentFromJson = EnhancedDocument.fromAttributeValueMap(stringKeyAttributeValueMao)});
+ *}
* @param attributeValueMap - Map with Attributes as String keys and AttributeValue as Value.
* @return A new instance of EnhancedDocument.
+ * @throws IllegalArgumentException if the json parameter is null
*/
static EnhancedDocument fromAttributeValueMap(Map attributeValueMap) {
- if (attributeValueMap == null) {
- return null;
- }
- return DefaultEnhancedDocument.builder()
- .attributeValueMap(attributeValueMap)
- .attributeConverterProviders(DefaultAttributeConverterProvider.create())
- .build();
- }
-
- /**
- * Convenience factory method - instantiates an EnhancedDocument from the given Map
- *
- * @param attributes Map of item attributes where each attribute should be a simple Java type, not DynamoDB type.
- * @return A new instance of EnhancedDocument.
- */
- static EnhancedDocument fromMap(Map attributes) {
- if (attributes == null) {
- return null;
- }
- DefaultEnhancedDocument.DefaultBuilder defaultBuilder = DefaultEnhancedDocument.builder();
- attributes.entrySet().forEach(key -> defaultBuilder.add(key.getKey(), key.getValue()));
- return defaultBuilder.addAttributeConverterProvider(DefaultAttributeConverterProvider.create())
- .build();
+ Validate.paramNotNull(attributeValueMap, "attributeValueMap");
+ return ((DefaultEnhancedDocument.DefaultBuilder) DefaultEnhancedDocument.builder())
+ .attributeValueMap(attributeValueMap)
+ .attributeConverterProviders(defaultProvider())
+ .build();
}
/**
@@ -163,7 +182,7 @@ static Builder builder() {
* @return value of the specified attribute in the current document as a number; or null if the attribute either doesn't exist
* or the attribute value is null
*/
- SdkNumber getSdkNumber(String attributeName);
+ SdkNumber getNumber(String attributeName);
/**
* Gets the {@link SdkBytes} value of specified attribute in the document.
@@ -172,20 +191,18 @@ static Builder builder() {
* @return the value of the specified attribute in the current document as SdkBytes; or null if the attribute either
* doesn't exist or the attribute value is null.
*/
- SdkBytes getSdkBytes(String attributeName);
+ SdkBytes getBytes(String attributeName);
/**
* Gets the Set of String values of the given attribute in the current document.
- *
- * @param attributeName Name of the attribute.
- * @return value of the specified attribute in the current document as a set of strings; or null if the attribute either
- * doesn't exist or the attribute value is null.
+ * @param attributeName the name of the attribute.
+ * @return the value of the specified attribute in the current document as a set of strings; or null if the attribute either
+ * does not exist or the attribute value is null.
*/
Set getStringSet(String attributeName);
/**
- * Gets the Set of {@link SdkNumber} values of the given attribute in the current document.
- *
+ * Gets the Set of String values of the given attribute in the current document.
* @param attributeName Name of the attribute.
* @return value of the specified attribute in the current document as a set of SdkNumber; or null if the attribute either
* doesn't exist or the attribute value is null.
@@ -193,13 +210,12 @@ static Builder builder() {
Set getNumberSet(String attributeName);
/**
- * Gets the Set of {@link SdkBytes} values of the given attribute in the current document.
- *
+ * Gets the Set of String values of the given attribute in the current document.
* @param attributeName Name of the attribute.
* @return value of the specified attribute in the current document as a set of SdkBytes; or null if the attribute either
* doesn't exist or the attribute value is null.
*/
- Set getSdkBytesSet(String attributeName);
+ Set getBytesSet(String attributeName);
/**
* Gets the List of values of type T for the given attribute in the current document.
@@ -213,70 +229,22 @@ static Builder builder() {
List getList(String attributeName, EnhancedType type);
-
- /**
- * Gets the List of values for the given attribute in the current document.
- * @param attributeName Name of the attribute.
- * @return value of the specified attribute in the current document as a list; or null if the
- * attribute either doesn't exist or the attribute value is null.
- */
- List> getList(String attributeName);
-
- /**
- * Gets the Map with Key as String and values as type T for the given attribute in the current document.
- *
Note that any numeric type of map is always canonicalized into {@link SdkNumber}, and therefore if T
- * referred to a Number type, it would need to be SdkNumber to avoid a class cast exception.
- *
- *
- * @param attributeName Name of the attribute.
- * @param type {@link EnhancedType} of Type T.
- * @param Type T of List elements
- * @return value of the specified attribute in the current document as a map of string-to-T's; or null if the
- * attribute either doesn't exist or the attribute value is null.
- */
- Map getMap(String attributeName, EnhancedType type);
-
- /**
- * Convenience method to return the specified attribute in the current item as a (copy of) map of
- * string-to-SdkNumber's where T must be a subclass of Number; or null if the attribute doesn't
- * exist.
- *
- * @param attributeName Name of the attribute.
- * @param valueType the specific number type of the value to be returned.
- * Currently, the supported types are
- *
- *
Short
- *
Integer
- *
Long
- *
Float
- *
Double
- *
Number
- *
BigDecimal
- *
BigInteger
- *
- * @return value of the specified attribute in the current item as a (copy of) map
- */
- Map getMapOfNumbers(String attributeName,
- Class valueType);
-
/**
- * Convenience method to return the value of the specified attribute in the current document as a map of
- * string-to-Object's; or null if the attribute either doesn't exist or the attribute value is null. Note that
- * any numeric type of the map will be returned as SdkNumber.
- *
- * @param attributeName Name of the attribute.
- * @return value of the specified attribute in the current document as a raw map.
- */
- Map getRawMap(String attributeName);
-
- /**
- * Gets the Map value of the specified attribute as an EnhancedDocument.
- *
- * @param attributeName Name of the attribute.
- * @return Map value of the specified attribute in the current document as EnhancedDocument or null if the attribute either
- * doesn't exist or the attribute value is null.
+ * Returns a map of a specific Key-type and Value-type based on the given attribute name, key type, and value type.
+ * Example usage: When getting an attribute as a map of {@link UUID} keys and {@link Integer} values, use this API
+ * as shown below:
+ * {@snippet :
+ Map result = document.getMap("key", EnhancedType.of(String.class), EnhancedType.of(Integer.class));
+ * }
+ * @param attributeName The name of the attribute that needs to be get as Map.
+ * @param keyType Enhanced Type of Key attribute, like String, UUID etc that can be represented as String Keys.
+ * @param valueType Enhanced Type of Values , which have converters defineds in
+ * {@link Builder#attributeConverterProviders(AttributeConverterProvider...)} for the document
+ * @return Map of type K and V with the given attribute name, key type, and value type.
+ * @param The type of the Map keys.
+ * @param The type of the Map values.
*/
- EnhancedDocument getMapAsDocument(String attributeName);
+ Map getMap(String attributeName, EnhancedType keyType, EnhancedType valueType);
/**
* Gets the JSON document value of the specified attribute.
@@ -287,15 +255,6 @@ Map getMapOfNumbers(String attributeName,
*/
String getJson(String attributeName);
- /**
- * Gets the JSON document value as pretty Json string for the specified attribute.
- *
- * @param attributeName Name of the attribute.
- * @return value of the specified attribute in the current document as a JSON string with pretty indentation; or null if the
- * attribute either doesn't exist or the attribute value is null.
- */
- String getJsonPretty(String attributeName);
-
/**
* Gets the {@link Boolean} value for the specified attribute.
*
@@ -307,41 +266,28 @@ Map getMapOfNumbers(String attributeName,
*/
Boolean getBoolean(String attributeName);
- /**
- * Gets the value as Object for a given attribute in the current document.
- * An attribute value can be a
- *
- *
Number
- *
String
- *
Binary (ie byte array or byte buffer)
- *
Boolean
- *
Null
- *
List (of any of the types on this list)
- *
Map (with string key to value of any of the types on this list)
- *
Set (of any of the types on this list)
- *
- *
- * @param attributeName Name of the attribute.
- * @return value of the specified attribute in the current document as an object; or null if the attribute either doesn't
- * exist or the attribute value is null.
- */
- Object get(String attributeName);
/**
- * Gets the EnhancedType for the specified attribute key
+ * Retrieves a list of {@link AttributeValue} objects for a specified attribute in a document.
+ * This API should be used when the elements of the list are a combination of different types such as Strings, Maps,
+ * and Numbers.
+ * If all elements in the list are of a known fixed type, use {@link EnhancedDocument#getList(String, EnhancedType)} instead.
*
* @param attributeName Name of the attribute.
- * @return type of the specified attribute in the current item; or null if the attribute either doesn't exist or the attribute
- * value is null.
+ * @return value of the specified attribute in the current document as a List of {@link AttributeValue}
*/
- EnhancedType> getTypeOf(String attributeName);
+ List getUnknownTypeList(String attributeName);
/**
- * Gets the current EnhancedDocument as Map.
+ * Retrieves a Map with String keys and corresponding AttributeValue objects as values for a specified attribute in a
+ * document. This API is particularly useful when the values of the map are of different types such as strings, maps, and
+ * numbers. However, if all the values in the map for a given attribute key are of a known fixed type, it is recommended to
+ * use the method EnhancedDocument#getMap(String, EnhancedType, EnhancedType) instead.
*
- * @return attributes of the current document as a map.
+ * @param attributeName Name of the attribute.
+ * @return value of the specified attribute in the current document as a {@link AttributeValue}
*/
- Map asMap();
+ Map getUnknownTypeMap(String attributeName);
/**
*
@@ -350,148 +296,178 @@ Map getMapOfNumbers(String attributeName,
String toJson();
/**
- * Gets the entire enhanced document as a pretty JSON string.
- *
- * @return document as a pretty JSON string. Note all binary data will become base-64 encoded in the resultant string
+ * This method converts a document into a key-value map with the keys as String objects and the values as AttributeValue
+ * objects. It can be particularly useful for documents with attributes of unknown types, as it allows for further processing
+ * or manipulation of the document data in a AttributeValue format.
+ * @return Document as a String AttributeValue Key-Value Map
*/
- String toJsonPretty();
+ Map toMap();
/**
- * Gets the current EnhancedDocument as a Map.
- * @return EnhancedDocument as a Map with Keys as String attributes and Values as AttributeValue.
+ *
+ * @return List of AttributeConverterProvider defined for the given Document.
*/
- Map toAttributeValueMap();
+ List attributeConverterProviders();
@NotThreadSafe
interface Builder {
- /**
- * Adds key attribute with the given value to the Document. An attribute value can be a
- *
- *
Number
- *
String
- *
Binary (ie byte array or byte buffer)
- *
Boolean
- *
Null
- *
List (of any of the types on this list)
- *
Map (with string key to value of any of the types on this list)
- *
Set (of any of the types on this list)
- *
- *
- * @param attributeName Name of the attribute that needs to be added in the Document builder.
- * @param value Value of the specified attribute
- * @return Builder instance to construct a {@link EnhancedDocument}
- */
- Builder add(String attributeName, Object value);
/**
* Appends an attribute of name attributeName with specified {@link String} value to the document builder.
+ * Use this method when you need to add a string value to a document. If you need to add an attribute with a value of a
+ * different type, such as a number or a map, use the appropriate put method instead
*
- * @param attributeName Name of the attribute that needs to be added in the Document.
+ * @param attributeName the name of the attribute to be added to the document.
* @param value The string value that needs to be set.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
- Builder addString(String attributeName, String value);
+ Builder putString(String attributeName, String value);
/**
* Appends an attribute of name attributeName with specified {@link Number} value to the document builder.
- *
- * @param attributeName Name of the attribute that needs to be added in the Document.
+ * Use this method when you need to add a number value to a document. If you need to add an attribute with a value of a
+ * different type, such as a string or a map, use the appropriate put method instead
+ * @param attributeName the name of the attribute to be added to the document.
* @param value The number value that needs to be set.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
- Builder addNumber(String attributeName, Number value);
+ Builder putNumber(String attributeName, Number value);
/**
- * Appends an attribute of name attributeName with specified {@link SdkBytes} value to the document builder.
- *
- * @param attributeName Name of the attribute that needs to be added in the Document.
+ * Appends an attribute of name attributeName with specified {@link SdkBytes} value to the document builder.
+ * Use this method when you need to add a binary value to a document. If you need to add an attribute with a value of a
+ * different type, such as a string or a map, use the appropriate put method instead
+ * @param attributeName the name of the attribute to be added to the document.
* @param value The byte array value that needs to be set.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
- Builder addSdkBytes(String attributeName, SdkBytes value);
+ Builder putBytes(String attributeName, SdkBytes value);
/**
- * Appends an attribute of name attributeName with specified boolean value to the document builder.
- *
- * @param attributeName Name of the attribute that needs to be added in the Document.
+ * Use this method when you need to add a boolean value to a document. If you need to add an attribute with a value of a
+ * different type, such as a string or a map, use the appropriate put method instead.
+ * @param attributeName the name of the attribute to be added to the document.
* @param value The boolean value that needs to be set.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
- Builder addBoolean(String attributeName, boolean value);
+ Builder putBoolean(String attributeName, Boolean value);
/**
* Appends an attribute of name attributeName with a null value.
+ * Use this method is the attribute needs to explicitly set to null in Dynamo DB table.
*
- * @param attributeName Name of the attribute that needs to be added in the Document.
+ * @param attributeName the name of the attribute to be added to the document.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
- Builder addNull(String attributeName);
+ Builder putNull(String attributeName);
/**
- * Appends an attribute of name attributeName with specified Set of {@link String} values to the document builder.
- *
- * @param attributeName Name of the attribute that needs to be added in the Document.
+ * Appends an attribute to the document builder with a Set of Strings as its value.
+ * This method is intended for use in DynamoDB where attribute values are stored as Sets of Strings.
+ * @param attributeName the name of the attribute to be added to the document.
* @param values Set of String values that needs to be set.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
- Builder addStringSet(String attributeName, Set values);
+ Builder putStringSet(String attributeName, Set values);
/**
* Appends an attribute of name attributeName with specified Set of {@link Number} values to the document builder.
*
- * @param attributeName Name of the attribute that needs to be added in the Document.
+ * @param attributeName the name of the attribute to be added to the document.
* @param values Set of Number values that needs to be set.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
- Builder addNumberSet(String attributeName, Set values);
+ Builder putNumberSet(String attributeName, Set values);
/**
* Appends an attribute of name attributeName with specified Set of {@link SdkBytes} values to the document builder.
*
- * @param attributeName Name of the attribute that needs to be added in the Document.
+ * @param attributeName the name of the attribute to be added to the document.
* @param values Set of SdkBytes values that needs to be set.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
- Builder addSdkBytesSet(String attributeName, Set values);
+ Builder putBytesSet(String attributeName, Set values);
/**
- * Appends an attribute of name attributeName with specified list of values to the document builder.
- *
- * @param attributeName Name of the attribute that needs to be added in the Document.
+ * Appends an attribute with the specified name and a list of {@link EnhancedType} T type elements to the document
+ * builder.
+ * Use {@link EnhancedType#of(Class)} to specify the class type of the list elements.
+ *
For example, to insert a list of String type:
+ * {@snippet :
+ * EnhancedDocument.builder().putList(stringList, EnhancedType.of(String.class))
+ * }
+ *
Example for inserting a List of Custom type .
+ * {@snippet :
+ * EnhancedDocument.builder().putList(stringList, EnhancedType.of(CustomClass.class));
+ * }
+ * Note that the AttributeConverterProvider added to the DocumentBuilder should provide the converter for the class T that
+ * is to be inserted.
+ * @param attributeName the name of the attribute to be added to the document.
* @param value The list of values that needs to be set.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
- Builder addList(String attributeName, List> value);
+ Builder putList(String attributeName, List value, EnhancedType type);
/**
- * Appends an attribute of name attributeName with specified map values to the document builder.
- *
- * @param attributeName Name of the attribute that needs to be added in the Document.
- * @param value The map that needs to be set.
- * @return Builder instance to construct a {@link EnhancedDocument}
+ * Appends an attribute named {@code attributeName} with a value of type {@link EnhancedType} T.
+ * Use this method to insert attribute values of custom types that have attribute converters defined in a converter
+ * provider.
+ * Example:
+ {@snippet :
+ EnhancedDocument.builder().putWithType("customKey", customValue, EnhancedType.of(CustomClass.class));
+ * }
+ * Use {@link #putString(String, String)} or {@link #putNumber(String, Number)} for inserting simple value types of
+ * attributes.
+ * Use {@link #putList(String, List, EnhancedType)} or {@link #putMapOfType(String, Map, EnhancedType, EnhancedType)} for
+ * inserting collections of attribute values.
+ * Note that the attribute converter provider added to the DocumentBuilder must provide the converter for the class T
+ * that is to be inserted.
+ @param attributeName the name of the attribute to be added to the document.
+ @param value the value to set.
+ @param type the type of the value to set.
+ @return a builder instance to construct a {@link EnhancedDocument}.
+ @param the type of the value to set.
*/
- Builder addMap(String attributeName, Map value);
+ Builder putWithType(String attributeName, T value, EnhancedType type);
/**
- * Appends an attribute of name attributeName with specified value of the given JSON document in the form of a string.
- *
- * @param attributeName Name of the attribute that needs to be added in the Document.
- * @param json JSON document in the form of a string.
+ * Appends an attribute with the specified name and a Map containing keys and values of {@link EnhancedType} K
+ * and V types,
+ * respectively, to the document builder. Use {@link EnhancedType#of(Class)} to specify the class type of the keys and
+ * values.
+ *
For example, to insert a map with String keys and Long values:
+ * {@snippet :
+ * EnhancedDocument.builder().putMapOfType("stringMap", mapWithStringKeyNumberValue, EnhancedType.of(String.class),
+ * EnhancedType.of(String.class), EnhancedType.of(Long.class))
+ * }
+ *
For example, to insert a map of String Key and Custom Values:
+ * {@snippet :
+ * EnhancedDocument.builder().putMapOfType("customMap", mapWithStringKeyCustomValue, EnhancedType.of(String.class),
+ * EnhancedType.of(String.class), EnhancedType.of(Custom.class))
+ * }
+ * Note that the AttributeConverterProvider added to the DocumentBuilder should provide the converter for the classes
+ * K and V that
+ * are to be inserted.
+ * @param attributeName the name of the attribute to be added to the document
+ * @param value The Map of values that needs to be set.
+ * @param keyType Enhanced type of Key class
+ * @param valueType Enhanced type of Value class.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
- Builder addJson(String attributeName, String json);
+ Builder putMapOfType(String attributeName, Map value, EnhancedType keyType, EnhancedType valueType);
/**
- * Appends an attribute of name attributeName with specified value of the given EnhancedDocument.
- * @param attributeName Name of the attribute that needs to be added in the Document.
- * @param enhancedDocument that needs to be added as a value to a key attribute.
+ Appends an attribute to the document builder with the specified name and value of a JSON document in string format.
+ * @param attributeName the name of the attribute to be added to the document.
+ * @param json JSON document in the form of a string.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
- Builder addEnhancedDocument(String attributeName, EnhancedDocument enhancedDocument);
+ Builder putJson(String attributeName, String json);
/**
* Appends collection of attributeConverterProvider to the document builder. These
* AttributeConverterProvider will be used to convert any given key to custom type T.
+ * The first matching converter from the given provider will be selected based on the order in which they are added.
* @param attributeConverterProvider determining the {@link AttributeConverter} to use for converting a value.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
@@ -500,7 +476,7 @@ interface Builder {
/**
* Sets the collection of attributeConverterProviders to the document builder. These AttributeConverterProvider will be
* used to convert value of any given key to custom type T.
- *
+ * The first matching converter from the given provider will be selected based on the order in which they are added.
* @param attributeConverterProviders determining the {@link AttributeConverter} to use for converting a value.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
@@ -509,17 +485,19 @@ interface Builder {
/**
* Sets collection of attributeConverterProviders to the document builder. These AttributeConverterProvider will be
* used to convert any given key to custom type T.
- *
+ * The first matching converter from the given provider will be selected based on the order in which they are added.
* @param attributeConverterProvider determining the {@link AttributeConverter} to use for converting a value.
* @return Builder instance to construct a {@link EnhancedDocument}
*/
Builder attributeConverterProviders(AttributeConverterProvider... attributeConverterProvider);
/**
- * Sets the entire JSON document in the form of a string to the document builder.
+ * Sets the attributes of the document builder to those specified in the provided JSON string, and completely replaces
+ * any previously set attributes.
*
- * @param json JSON document in the form of a string.
- * @return Builder instance to construct a {@link EnhancedDocument}
+ * @param json a JSON document represented as a string
+ * @return a builder instance to construct a {@link EnhancedDocument}
+ * @throws NullPointerException if the json parameter is null
*/
Builder json(String json);
@@ -530,4 +508,5 @@ interface Builder {
*/
EnhancedDocument build();
}
+
}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/JsonItemAttributeConverter.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/JsonItemAttributeConverter.java
index 4ebd6397ca41..dd9d9b3d4fb2 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/JsonItemAttributeConverter.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/JsonItemAttributeConverter.java
@@ -73,6 +73,9 @@ public AttributeValue transformFrom(JsonNode input) {
@Override
public JsonNode transformTo(AttributeValue input) {
+ if (AttributeValue.fromNul(true).equals(input)) {
+ return NullJsonNode.instance();
+ }
return EnhancedAttributeValue.fromAttributeValue(input).convert(VISITOR);
}
@@ -132,7 +135,7 @@ public JsonNode convertSetOfStrings(List value) {
if (value == null) {
return null;
}
- return new ArrayJsonNode(value.stream().map(s -> new StringJsonNode(s)).collect(Collectors.toList()));
+ return new ArrayJsonNode(value.stream().map(StringJsonNode::new).collect(Collectors.toList()));
}
@Override
@@ -140,7 +143,7 @@ public JsonNode convertSetOfNumbers(List value) {
if (value == null) {
return null;
}
- return new ArrayJsonNode(value.stream().map(s -> new NumberJsonNode(s)).collect(Collectors.toList()));
+ return new ArrayJsonNode(value.stream().map(NumberJsonNode::new).collect(Collectors.toList()));
}
@Override
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/JsonNodeToAttributeValueMapConverter.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/JsonNodeToAttributeValueMapConverter.java
index fbae285d60d0..f7a8b6643f34 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/JsonNodeToAttributeValueMapConverter.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/JsonNodeToAttributeValueMapConverter.java
@@ -38,7 +38,7 @@ public static JsonNodeToAttributeValueMapConverter instance() {
@Override
public AttributeValue visitNull() {
- return AttributeValue.builder().build();
+ return AttributeValue.fromNul(true);
}
@Override
@@ -68,9 +68,10 @@ public AttributeValue visitArray(List array) {
public AttributeValue visitObject(Map object) {
return AttributeValue.builder().m(object.entrySet().stream()
.collect(Collectors.toMap(
- entry -> entry.getKey(),
+ Map.Entry::getKey,
entry -> entry.getValue().visit(this),
- (left, right) -> left, LinkedHashMap::new))).build();
+ (left, right) -> left, LinkedHashMap::new)))
+ .build();
}
@Override
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/SdkNumberAttributeConverter.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/SdkNumberAttributeConverter.java
new file mode 100644
index 000000000000..80b983fa2076
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/SdkNumberAttributeConverter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute;
+
+import software.amazon.awssdk.annotations.Immutable;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.annotations.ThreadSafe;
+import software.amazon.awssdk.core.SdkNumber;
+import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
+import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
+import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
+import software.amazon.awssdk.enhanced.dynamodb.internal.converter.TypeConvertingVisitor;
+import software.amazon.awssdk.enhanced.dynamodb.internal.converter.string.SdkNumberStringConverter;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+
+/**
+ * A converter between {@link SdkNumber} and {@link AttributeValue}.
+ *
+ *
+ * This stores values in DynamoDB as a number.
+ *
+ *
+ * This supports reading the full range of integers supported by DynamoDB. For smaller numbers, consider using
+ * {@link ShortAttributeConverter}, {@link IntegerAttributeConverter} or {@link LongAttributeConverter}.
+ *
+ * This can be created via {@link #create()}.
+ */
+@SdkInternalApi
+@ThreadSafe
+@Immutable
+public final class SdkNumberAttributeConverter implements AttributeConverter {
+ private static final Visitor VISITOR = new Visitor();
+ private static final SdkNumberStringConverter STRING_CONVERTER = SdkNumberStringConverter.create();
+
+ private SdkNumberAttributeConverter() {
+ }
+
+ public static SdkNumberAttributeConverter create() {
+ return new SdkNumberAttributeConverter();
+ }
+
+ @Override
+ public EnhancedType type() {
+ return EnhancedType.of(SdkNumber.class);
+ }
+
+ @Override
+ public AttributeValueType attributeValueType() {
+ return AttributeValueType.N;
+ }
+
+ @Override
+ public AttributeValue transformFrom(SdkNumber input) {
+ return AttributeValue.builder().n(STRING_CONVERTER.toString(input)).build();
+ }
+
+ @Override
+ public SdkNumber transformTo(AttributeValue input) {
+ if (input.n() != null) {
+ return EnhancedAttributeValue.fromNumber(input.n()).convert(VISITOR);
+ }
+ return EnhancedAttributeValue.fromAttributeValue(input).convert(VISITOR);
+ }
+
+ private static final class Visitor extends TypeConvertingVisitor {
+ private Visitor() {
+ super(SdkNumber.class, SdkNumberAttributeConverter.class);
+ }
+
+ @Override
+ public SdkNumber convertString(String value) {
+ return STRING_CONVERTER.fromString(value);
+ }
+
+ @Override
+ public SdkNumber convertNumber(String value) {
+ return STRING_CONVERTER.fromString(value);
+ }
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/string/SdkNumberStringConverter.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/string/SdkNumberStringConverter.java
new file mode 100644
index 000000000000..c34ecfb982c3
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/string/SdkNumberStringConverter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.internal.converter.string;
+
+import software.amazon.awssdk.annotations.Immutable;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.annotations.ThreadSafe;
+import software.amazon.awssdk.core.SdkNumber;
+import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
+import software.amazon.awssdk.enhanced.dynamodb.internal.converter.StringConverter;
+
+/**
+ * A converter between {@link SdkNumber} and {@link String}.
+ *
+ *
+ * This converts values using {@link SdkNumber#toString()} and {@link SdkNumber#fromString(String)}}.
+ */
+@SdkInternalApi
+@ThreadSafe
+@Immutable
+public class SdkNumberStringConverter implements StringConverter {
+ private SdkNumberStringConverter() {
+ }
+
+ public static SdkNumberStringConverter create() {
+ return new SdkNumberStringConverter();
+ }
+
+ @Override
+ public EnhancedType type() {
+ return EnhancedType.of(SdkNumber.class);
+ }
+
+ @Override
+ public SdkNumber fromString(String string) {
+ return SdkNumber.fromString(string);
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DefaultEnhancedDocument.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DefaultEnhancedDocument.java
index b90c391ecf6d..256cde2f0503 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DefaultEnhancedDocument.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DefaultEnhancedDocument.java
@@ -15,514 +15,427 @@
package software.amazon.awssdk.enhanced.dynamodb.internal.document;
-import static software.amazon.awssdk.enhanced.dynamodb.internal.document.DocumentUtils.NULL_ATTRIBUTE_VALUE;
-import static software.amazon.awssdk.enhanced.dynamodb.internal.document.DocumentUtils.convert;
-import static software.amazon.awssdk.enhanced.dynamodb.internal.document.DocumentUtils.convertAttributeValueToObject;
-import static software.amazon.awssdk.enhanced.dynamodb.internal.document.DocumentUtils.toSimpleList;
-import static software.amazon.awssdk.enhanced.dynamodb.internal.document.DocumentUtils.toSimpleMapValue;
-import static software.amazon.awssdk.enhanced.dynamodb.internal.document.DocumentUtils.toSimpleValue;
+import static java.util.Collections.unmodifiableList;
+import static java.util.Collections.unmodifiableMap;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import software.amazon.awssdk.annotations.Immutable;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.core.SdkNumber;
-import software.amazon.awssdk.core.document.Document;
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
-import software.amazon.awssdk.enhanced.dynamodb.DefaultAttributeConverterProvider;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.ChainConverterProvider;
+import software.amazon.awssdk.enhanced.dynamodb.internal.converter.StringConverterProvider;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.JsonItemAttributeConverter;
-import software.amazon.awssdk.protocols.json.internal.unmarshall.document.DocumentUnmarshaller;
+import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.ListAttributeConverter;
+import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.MapAttributeConverter;
import software.amazon.awssdk.protocols.jsoncore.JsonNode;
import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.utils.Lazy;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.Validate;
/**
- * Default implementation of {@link EnhancedDocument}. This class is used by SDK to create Enhanced Documents.
- * Internally saves attributes in an attributeValueMap which can be written to DynamoDB without further conversion.
- * The attribute values are retrieved by converting attributeValue from attributeValueMap at the time of get.
+ * Default implementation of {@link EnhancedDocument} used by the SDK to create Enhanced Documents. Attributes are initially saved
+ * as a String-Object Map when documents are created using the builder. Conversion to an AttributeValueMap is done lazily when
+ * values are accessed. When the document is retrieved from DynamoDB, the AttributeValueMap is internally saved as the attribute
+ * value map. Custom objects or collections are saved in the enhancedTypeMap to preserve the generic class information. Note that
+ * no default ConverterProviders are assigned, so ConverterProviders must be passed in the builder when creating enhanced
+ * documents.
*/
@Immutable
@SdkInternalApi
public class DefaultEnhancedDocument implements EnhancedDocument {
- private static final JsonItemAttributeConverter JSON_ITEM_ATTRIBUTE_CONVERTER = JsonItemAttributeConverter.create();
-
- private final Map attributeValueMap;
-
- private final ChainConverterProvider attributeConverterProviders;
-
- private DefaultEnhancedDocument(Map attributeValueMap) {
- this.attributeValueMap = attributeValueMap;
- this.attributeConverterProviders = ChainConverterProvider.create(DefaultAttributeConverterProvider.create());
- }
+ public static final IllegalStateException NULL_SET_ERROR = new IllegalStateException("Set must not have null values.");
+ private static final JsonItemAttributeConverter JSON_ATTRIBUTE_CONVERTER = JsonItemAttributeConverter.create();
+ private final Map nonAttributeValueMap;
+ private final Map enhancedTypeMap;
+ private final List attributeConverterProviders;
+ private final ChainConverterProvider attributeConverterChain;
+ private final Lazy