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
new file mode 100644
index 000000000000..0ade5fb9b495
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.java
@@ -0,0 +1,512 @@
+/*
+ * 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.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.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 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 {
+
+ /**
+ * 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) {
+ Validate.paramNotNull(json, "json");
+ return DefaultEnhancedDocument.builder()
+ .json(json)
+ .attributeConverterProviders(defaultProvider())
+ .build();
+ }
+
+ /**
+ * 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) {
+ Validate.paramNotNull(attributeValueMap, "attributeValueMap");
+ return ((DefaultEnhancedDocument.DefaultBuilder) DefaultEnhancedDocument.builder())
+ .attributeValueMap(attributeValueMap)
+ .attributeConverterProviders(defaultProvider())
+ .build();
+ }
+
+ /**
+ * Creates a default builder for {@link EnhancedDocument}.
+ */
+ static Builder builder() {
+ return DefaultEnhancedDocument.builder();
+ }
+
+ /**
+ * Converts an existing EnhancedDocument into a builder object that can be used to modify its values and then create a new
+ * EnhancedDocument.
+ *
+ * @return A {@link EnhancedDocument.Builder} initialized with the values of this EnhancedDocument.
+ */
+ Builder toBuilder();
+
+ /**
+ * Checks if the document is a {@code null} value.
+ *
+ * @param attributeName Name of the attribute that needs to be checked.
+ * @return true if the specified attribute exists with a null value; false otherwise.
+ */
+ boolean isNull(String attributeName);
+
+ /**
+ * Checks if the attribute exists in the document.
+ *
+ * @param attributeName Name of the attribute that needs to be checked.
+ * @return true if the specified attribute exists with a null/non-null value; false otherwise.
+ */
+ boolean isPresent(String attributeName);
+
+ /**
+ * Returns the value of the specified attribute in the current document as a specified {@link EnhancedType}; or null if the
+ * attribute either doesn't exist or the attribute value is null.
+ *
+ * Retrieving String Type for a document
+ * {@snippet :
+ * Custom resultCustom = document.get("key", EnhancedType.of(Custom.class));
+ * }
+ * Retrieving Custom Type for which Convertor Provider was defined while creating the document
+ * {@snippet :
+ * Custom resultCustom = document.get("key", EnhancedType.of(Custom.class));
+ * }
+ * Retrieving list of strings in a document
+ * {@snippet :
+ * List resultList = document.get("key", EnhancedType.listOf(String.class));
+ * }
+ * Retrieving a Map with List of strings in its values
+ * {@snippet :
+ * Map>> resultNested = document.get("key", new EnhancedType
+ * @param attributeName Name of the attribute.
+ * @param type EnhancedType of the value
+ * @param The type of the attribute value.
+ * @return Attribute value of type T
+ * }
+ */
+ T get(String attributeName, EnhancedType type);
+
+ /**
+ * Gets the String value of specified attribute in the document.
+ *
+ * @param attributeName Name of the attribute.
+ * @return value of the specified attribute in the current document as a string; or null if the attribute either doesn't exist
+ * or the attribute value is null
+ */
+ String getString(String attributeName);
+
+ /**
+ * Gets the {@link SdkNumber} value of specified attribute in the document.
+ *
+ * @param attributeName Name of the attribute.
+ * @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 getNumber(String attributeName);
+
+ /**
+ * Gets the {@link SdkBytes} value of specified attribute in the document.
+ *
+ * @param attributeName Name of the attribute.
+ * @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 getBytes(String attributeName);
+
+ /**
+ * Gets the Set of String values of the given attribute in the current document.
+ * @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 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.
+ */
+ Set getNumberSet(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 SdkBytes; or null if the attribute either
+ * doesn't exist or the attribute value is null.
+ */
+ Set getBytesSet(String attributeName);
+
+ /**
+ * Gets the List of values of type T for the given attribute in the current document.
+ *
+ * @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 list of type T; or null if the
+ * attribute either doesn't exist or the attribute value is null.
+ */
+
+ List getList(String attributeName, EnhancedType type);
+
+ /**
+ * 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.
+ */
+ Map getMap(String attributeName, EnhancedType keyType, EnhancedType valueType);
+
+ /**
+ * Gets the JSON document value of the specified attribute.
+ *
+ * @param attributeName Name of the attribute.
+ * @return value of the specified attribute in the current document as a JSON string; or null if the attribute either
+ * doesn't exist or the attribute value is null.
+ */
+ String getJson(String attributeName);
+
+ /**
+ * Gets the {@link Boolean} value for the specified attribute.
+ *
+ * @param attributeName Name of the attribute.
+ * @return value of the specified attribute in the current document as a non-null Boolean.
+ * @throws RuntimeException
+ * if either the attribute doesn't exist or if the attribute
+ * value cannot be converted into a boolean value.
+ */
+ Boolean getBoolean(String attributeName);
+
+
+ /**
+ * 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 value of the specified attribute in the current document as a List of {@link AttributeValue}
+ */
+ List getUnknownTypeList(String attributeName);
+
+ /**
+ * 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.
+ *
+ * @param attributeName Name of the attribute.
+ * @return value of the specified attribute in the current document as a {@link AttributeValue}
+ */
+ Map getUnknownTypeMap(String attributeName);
+
+ /**
+ *
+ * @return document as a JSON string. Note all binary data will become base-64 encoded in the resultant string.
+ */
+ String toJson();
+
+ /**
+ * 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
+ */
+ Map toMap();
+
+ /**
+ *
+ * @return List of AttributeConverterProvider defined for the given Document.
+ */
+ List attributeConverterProviders();
+
+ @NotThreadSafe
+ interface Builder {
+
+ /**
+ * 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 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 putString(String attributeName, String value);
+
+ /**
+ * Appends an attribute of name attributeName with specified {@link Number} value to the document builder.
+ * 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 putNumber(String attributeName, Number value);
+
+ /**
+ * 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 putBytes(String attributeName, SdkBytes value);
+
+ /**
+ * 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 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 the name of the attribute to be added to the document.
+ * @return Builder instance to construct a {@link EnhancedDocument}
+ */
+ Builder putNull(String attributeName);
+
+ /**
+ * 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 putStringSet(String attributeName, Set values);
+
+ /**
+ * Appends an attribute of name attributeName with specified Set of {@link Number} values to the document builder.
+ *
+ * @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 putNumberSet(String attributeName, Set values);
+
+ /**
+ * Appends an attribute of name attributeName with specified Set of {@link SdkBytes} values to the document builder.
+ *
+ * @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 putBytesSet(String attributeName, Set values);
+
+ /**
+ * 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 putList(String attributeName, List value, EnhancedType type);
+
+ /**
+ * 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 putWithType(String attributeName, T value, EnhancedType type);
+
+ /**
+ * 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 putMapOfType(String attributeName, Map value, EnhancedType keyType, EnhancedType valueType);
+
+ /**
+ 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 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}
+ */
+ Builder addAttributeConverterProvider(AttributeConverterProvider attributeConverterProvider);
+
+ /**
+ * 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}
+ */
+ Builder attributeConverterProviders(List attributeConverterProviders);
+
+ /**
+ * 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 attributes of the document builder to those specified in the provided JSON string, and completely replaces
+ * any previously set attributes.
+ *
+ * @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);
+
+ /**
+ * Builds an instance of {@link EnhancedDocument}.
+ *
+ * @return instance of {@link EnhancedDocument} implementation.
+ */
+ EnhancedDocument build();
+ }
+
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ChainConverterProvider.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ChainConverterProvider.java
index a455051adb5f..8646810102dc 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ChainConverterProvider.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/ChainConverterProvider.java
@@ -19,6 +19,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
@@ -67,4 +68,22 @@ public AttributeConverter converterFor(EnhancedType enhancedType) {
.map(p -> p.converterFor(enhancedType))
.findFirst().orElse(null);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ChainConverterProvider that = (ChainConverterProvider) o;
+ return Objects.equals(providerChain, that.providerChain);
+ }
+
+ @Override
+ public int hashCode() {
+ return providerChain != null ? providerChain.hashCode() : 0;
+ }
+
}
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
new file mode 100644
index 000000000000..dd9d9b3d4fb2
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/JsonItemAttributeConverter.java
@@ -0,0 +1,171 @@
+/*
+ * 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 java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import software.amazon.awssdk.annotations.Immutable;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.annotations.ThreadSafe;
+import software.amazon.awssdk.core.SdkBytes;
+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.protocols.jsoncore.JsonNode;
+import software.amazon.awssdk.protocols.jsoncore.internal.ArrayJsonNode;
+import software.amazon.awssdk.protocols.jsoncore.internal.BooleanJsonNode;
+import software.amazon.awssdk.protocols.jsoncore.internal.NullJsonNode;
+import software.amazon.awssdk.protocols.jsoncore.internal.NumberJsonNode;
+import software.amazon.awssdk.protocols.jsoncore.internal.ObjectJsonNode;
+import software.amazon.awssdk.protocols.jsoncore.internal.StringJsonNode;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+
+/**
+ * An Internal converter between JsonNode and {@link AttributeValue}.
+ *
+ *
+ * This converts the Attribute Value read from the DDB to JsonNode.
+ */
+@SdkInternalApi
+@ThreadSafe
+@Immutable
+public final class JsonItemAttributeConverter implements AttributeConverter {
+ private static final Visitor VISITOR = new Visitor();
+
+ private JsonItemAttributeConverter() {
+ }
+
+ public static JsonItemAttributeConverter create() {
+ return new JsonItemAttributeConverter();
+ }
+
+ @Override
+ public EnhancedType type() {
+ return EnhancedType.of(JsonNode.class);
+ }
+
+ @Override
+ public AttributeValueType attributeValueType() {
+ return AttributeValueType.M;
+ }
+
+ @Override
+ public AttributeValue transformFrom(JsonNode input) {
+ JsonNodeToAttributeValueMapConverter attributeValueMapConverter = JsonNodeToAttributeValueMapConverter.instance();
+ return input.visit(attributeValueMapConverter);
+ }
+
+ @Override
+ public JsonNode transformTo(AttributeValue input) {
+ if (AttributeValue.fromNul(true).equals(input)) {
+ return NullJsonNode.instance();
+ }
+ return EnhancedAttributeValue.fromAttributeValue(input).convert(VISITOR);
+ }
+
+ private static final class Visitor extends TypeConvertingVisitor {
+ private Visitor() {
+ super(JsonNode.class, JsonItemAttributeConverter.class);
+ }
+
+ @Override
+ public JsonNode convertMap(Map value) {
+ if (value == null) {
+ return null;
+ }
+ Map jsonNodeMap = new LinkedHashMap<>();
+ value.entrySet().forEach(
+ k -> {
+ JsonNode jsonNode = this.convert(EnhancedAttributeValue.fromAttributeValue(k.getValue()));
+ jsonNodeMap.put(k.getKey(), jsonNode == null ? NullJsonNode.instance() : jsonNode);
+ });
+ return new ObjectJsonNode(jsonNodeMap);
+ }
+
+ @Override
+ public JsonNode convertString(String value) {
+ if (value == null) {
+ return null;
+ }
+ return new StringJsonNode(value);
+ }
+
+ @Override
+ public JsonNode convertNumber(String value) {
+ if (value == null) {
+ return null;
+ }
+ return new NumberJsonNode(value);
+ }
+
+ @Override
+ public JsonNode convertBytes(SdkBytes value) {
+ if (value == null) {
+ return null;
+ }
+ return new StringJsonNode(value.asUtf8String());
+ }
+
+ @Override
+ public JsonNode convertBoolean(Boolean value) {
+ if (value == null) {
+ return null;
+ }
+ return new BooleanJsonNode(value);
+ }
+
+ @Override
+ public JsonNode convertSetOfStrings(List value) {
+ if (value == null) {
+ return null;
+ }
+ return new ArrayJsonNode(value.stream().map(StringJsonNode::new).collect(Collectors.toList()));
+ }
+
+ @Override
+ public JsonNode convertSetOfNumbers(List value) {
+ if (value == null) {
+ return null;
+ }
+ return new ArrayJsonNode(value.stream().map(NumberJsonNode::new).collect(Collectors.toList()));
+ }
+
+ @Override
+ public JsonNode convertSetOfBytes(List value) {
+ if (value == null) {
+ return null;
+ }
+ return new ArrayJsonNode(value.stream().map(sdkByte ->
+ new StringJsonNode(sdkByte.asUtf8String())
+ ).collect(Collectors.toList()));
+ }
+
+ @Override
+ public JsonNode convertListOfAttributeValues(List value) {
+ if (value == null) {
+ return null;
+ }
+ return new ArrayJsonNode(value.stream().map(
+ attributeValue -> {
+ EnhancedAttributeValue enhancedAttributeValue = EnhancedAttributeValue.fromAttributeValue(attributeValue);
+ return enhancedAttributeValue.isNull() ? NullJsonNode.instance() : enhancedAttributeValue.convert(VISITOR);
+ }).collect(Collectors.toList()));
+ }
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 000000000000..f7a8b6643f34
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/JsonNodeToAttributeValueMapConverter.java
@@ -0,0 +1,81 @@
+/*
+ * 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 java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.protocols.jsoncore.JsonNode;
+import software.amazon.awssdk.protocols.jsoncore.JsonNodeVisitor;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+
+@SdkInternalApi
+public class JsonNodeToAttributeValueMapConverter implements JsonNodeVisitor {
+
+ private static final JsonNodeToAttributeValueMapConverter INSTANCE = new JsonNodeToAttributeValueMapConverter();
+
+ private JsonNodeToAttributeValueMapConverter() {
+ }
+
+ public static JsonNodeToAttributeValueMapConverter instance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public AttributeValue visitNull() {
+ return AttributeValue.fromNul(true);
+ }
+
+ @Override
+ public AttributeValue visitBoolean(boolean bool) {
+ return AttributeValue.builder().bool(bool).build();
+ }
+
+ @Override
+ public AttributeValue visitNumber(String number) {
+ return AttributeValue.builder().n(number).build();
+ }
+
+ @Override
+ public AttributeValue visitString(String string) {
+ return AttributeValue.builder().s(string).build();
+ }
+
+ @Override
+ public AttributeValue visitArray(List array) {
+ return AttributeValue.builder().l(array.stream()
+ .map(node -> node.visit(this))
+ .collect(Collectors.toList()))
+ .build();
+ }
+
+ @Override
+ public AttributeValue visitObject(Map object) {
+ return AttributeValue.builder().m(object.entrySet().stream()
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> entry.getValue().visit(this),
+ (left, right) -> left, LinkedHashMap::new)))
+ .build();
+ }
+
+ @Override
+ public AttributeValue visitEmbeddedObject(Object embeddedObject) {
+ throw new UnsupportedOperationException("Embedded objects are not supported within Document types.");
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 000000000000..beb233f197c0
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DefaultEnhancedDocument.java
@@ -0,0 +1,460 @@
+/*
+ * 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.document;
+
+import static java.util.Collections.unmodifiableList;
+import static java.util.Collections.unmodifiableMap;
+import static software.amazon.awssdk.enhanced.dynamodb.internal.document.JsonStringFormatHelper.addEscapeCharacters;
+import static software.amazon.awssdk.enhanced.dynamodb.internal.document.JsonStringFormatHelper.stringValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+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.enhanced.dynamodb.AttributeConverter;
+import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
+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.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.Validate;
+
+/**
+ * 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 {
+
+ 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