Manually define schemas
Instead of generating schemas from annotations, you can define them directly in code. Manual schemas give you full control over how objects map to items and don’t require the schema-generator plugin. They’re useful when you want explicit control over conversion, when you can’t annotate a type (for example, a class from another library), or when you map polymorphic or document-shaped data.
A schema has two parts:
-
An item converter (ItemConverter) that converts between your object and a DynamoDB item.
-
A key specification (KeySpec) that identifies primary key fields.
You combine them into an ItemSchema, then pass that schema to getTable. This page builds a schema for the Product class (a partition-key-only item) as the worked example.
data class Product( val sku: String, val name: String, val category: String, val priceCents: Long, )
Item converters
An ItemConverter<T> converts between objects of type T and DynamoDB items. It’s a type alias for Converter<T, Item> and defines two methods:
-
convertRight(from: T): Item: convert your object to an item for writes. -
convertLeft(from: Item): T: convert an item to your object (for reads).
You may implement ItemConverter from scratch or use one of the built-in implementations described in the following sections.
Build a converter with SimpleItemConverter
SimpleItemConverter builds an item attribute by attribute. It separates the potentially-immutable object type T from a mutable builder type B used when reading items back. You provide:
-
builderFactory: a function which creates a fresh builder. -
build: a function which finalizes a builder into aT. -
One
AttributeDescriptorper attribute, describing its name, how to read it fromT, how to write it onto the builder, and which value converter handles its type.
For example, assuming the type Product is immutable, define a small builder for it:
class ProductBuilder { var sku: String? = null var name: String? = null var category: String? = null var priceCents: Long? = null fun build() = Product( sku = requireNotNull(sku) { "sku is required" }, name = requireNotNull(name) { "name is required" }, category = requireNotNull(category) { "category is required" }, priceCents = requireNotNull(priceCents) { "priceCents is required" }, ) }
Then assemble the converter. Each AttributeDescriptor pairs a property with a built-in value converter (StringValueConverter for the String attributes and NumberValueConverters.Long for the Long price):
import aws.sdk.kotlin.hll.dynamodbmapper.items.AttributeDescriptor import aws.sdk.kotlin.hll.dynamodbmapper.items.SimpleItemConverter import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.NumberValueConverters import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringValueConverter val productConverter = SimpleItemConverter( builderFactory = ::ProductBuilder, build = ProductBuilder::build, AttributeDescriptor( name = "sku", getter = Product::sku, setter = ProductBuilder::sku::set, converter = StringValueConverter, ), AttributeDescriptor( name = "name", getter = Product::name, setter = ProductBuilder::name::set, converter = StringValueConverter, ), AttributeDescriptor( name = "category", getter = Product::category, setter = ProductBuilder::category::set, converter = StringValueConverter, ), AttributeDescriptor( name = "priceCents", getter = Product::priceCents, setter = ProductBuilder::priceCents::set, converter = NumberValueConverters.Long, ), )
By default, attributes present on a stored item but absent from the descriptors are ignored when reading. Pass unknownValueHandling to SimpleItemConverter to throw or handle them instead.
Define keys with KeySpec
A KeySpec names the key attributes and their types, which DynamoDB Mapper needs in order to build key conditions for query and scan. Create a single-attribute spec with one of the KeySpec companion functions (in alphabetical order: byte, byteArray, int, long, short, and string):
import aws.sdk.kotlin.hll.dynamodbmapper.items.KeySpec val skuKey = KeySpec.string("sku") // KeySpec.Key1<String>
Important
The attribute name you pass must exactly match the key attribute defined on your DynamoDB table or index.
For a composite key, the partition and sort keys are two separate KeySpec instances (see Assemble an ItemSchema). A single KeySpec can also describe up to four attributes by chaining thenInt, thenLong, thenString, and the like. This is used for multi-attribute index keys, not table primary keys.
Assemble an ItemSchema
Combine the converter and key spec into an ItemSchema. For a partition-key-only item like Product, pass the converter and a single partition key:
import aws.sdk.kotlin.hll.dynamodbmapper.items.ItemSchema val productSchema = ItemSchema( converter = productConverter, partitionKey = KeySpec.string("sku"), ) val productsTable = mapper.getTable("products", productSchema)
For a composite-key item, supply both keys:
val orderSchema = ItemSchema( converter = orderConverter, partitionKey = KeySpec.string("customerId"), sortKey = KeySpec.string("orderId"), )
Equivalently, call withKeySpec on a converter:
import aws.sdk.kotlin.hll.dynamodbmapper.items.withKeySpec val productSchema = productConverter.withKeySpec(KeySpec.string("sku"))
Value converters
A ValueConverter<V> converts a single value between your type V and a DynamoDB attribute value. Like ItemConverter, it defines convertRight (V → attribute value) and convertLeft (attribute value → V). The SDK ships value converters for the common types, so you usually reference an existing one rather than write your own. The following table lists representative built-in converters, in alphabetical order:
| Converter | Kotlin type | Package |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum types |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
numeric types |
|
|
|
|
|
|
|
|
|
The full package prefix is aws.sdk.kotlin.hll.dynamodbmapper.values. Set and number-set converters live in …values.collections.
To support a type that has no built-in converter, implement ValueConverter yourself. For example, a converter for kotlin.uuid.Uuid stored as a DynamoDB string:
import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter import aws.sdk.kotlin.services.dynamodb.model.AttributeValue import kotlin.uuid.Uuid object UuidConverter : ValueConverter<Uuid> { override fun convertRight(from: Uuid): AttributeValue = AttributeValue.S(from.toString()) override fun convertLeft(from: AttributeValue): Uuid = Uuid.parse(from.asS()) }
You can reference a custom value converter from a SimpleItemConverter attribute descriptor, or, when generating schemas from annotations, from @DynamoDbAttributeConverter.
Other item converters
Two more built-in ItemConverter implementations cover specialized mappings:
HeterogeneousItemConverter
HeterogeneousItemConverter maps polymorphic or heterogeneous types by using a string discriminator attribute and delegating to per-subtype converters. You provide:
* typeMapper: a function which returns the discriminator value for an object
* typeAttribute: the item attribute name which stores the discriminator
* subConverters: a map from discriminator values to ItemConverters for each subtype. Each subtype converter is an ordinary ItemConverter such as a SimpleItemConverter built as shown earlier in this topic).
For example:
import aws.sdk.kotlin.hll.dynamodbmapper.items.HeterogeneousItemConverter sealed interface PaymentMethod { data class CreditCard(val customerId: String, val methodId: String, val last4: String) : PaymentMethod data class BankAccount(val customerId: String, val methodId: String, val routing: String) : PaymentMethod data class GiftCard(val customerId: String, val methodId: String, val balanceCents: Long) : PaymentMethod } fun paymentType(p: PaymentMethod): String = when (p) { is PaymentMethod.CreditCard -> "credit_card" is PaymentMethod.BankAccount -> "bank_account" is PaymentMethod.GiftCard -> "gift_card" } val paymentConverter = HeterogeneousItemConverter( typeMapper = ::paymentType, typeAttribute = "type", subConverters = mapOf( "bank_account" to bankAccountConverter, "credit_card" to creditCardConverter, "gift_card" to giftCardConverter, ), )
Each object is stored using only the attributes relevant to its subtype, plus the discriminator.
DocumentItemConverter
DocumentItemConverter maps a smithy-kotlin Document.Map to and from an item, which is handy for schemaless or dynamically shaped data. Use DocumentItemConverter.Default for the standard configuration.
Related topics
-
Generate a schema from annotations: let the plugin generate schemas for you.
-
DynamoDB Mapper annotations reference: the annotations (such as
@DynamoDbItemand@DynamoDbAttributeConverter) that the schema generator reads. -
Operations overview: use the table you obtained from your schema.
-
Use secondary indexes with DynamoDB Mapper: multi-attribute index keys with
KeySpec.