DynamoDB format for kotlinx.serialization. Serialize and deserialize documents from DynamoDB using kotlinx.serialization
. This library allows you to map DynamoDB documents to / from domain object.
According with Wikipedia, Amazon DynamoDB is a fully managed proprietary NoSQL database offered by Amazon.com.
When using DynamoDB - Kotlin SDK, the SDK returns documents using AttributeValue. This library allows you to easily convert AttributeValue to Kotlin objects using the power of kotlinx.serialization
.
To use the DynaMap library, you have to perform serialization and deserialization similar to how you would with kotlinx.serialization.
- Install
kotlinx.serialization
plugin. - Add
DynaMap
serialization dependency.
Kotlin DSL:
plugins {
kotlin("jvm")
// ADD SERIALIZATION PLUGIN
kotlin("plugin.serialization")
}
dependencies {
// ADD SERIALIZATION DEPENDENCY
implementation("com.codanbaru.kotlin:dynamap:0.8.0")
}
Groovy DSL
plugins {
// ADD SERIALIZATION PLUGIN
id 'org.jetbrains.kotlin.plugin.serialization'
}
dependencies {
// ADD SERIALIZATION DEPENDENCY
implementation 'com.codanbaru.kotlin:dynamap:0.8.0'
}
@Serializable
data class Book(val name: String, val author: String?)
val dynamap = Dynamap {
evaluateUndefinedAttributesAsNullAttribute = false
}
fun encodeBook(book: Book): Map<String, AttributeValue> {
val item: Map<String, AttributeValue> = dynamap.encodeToItem(book)
return item
}
fun decodeBook(item: Map<String, AttributeValue>): Book {{
val obj: Book = dynamap.decodeFromItem(item)
return obj
}
By default, the DynaMap
library evaluates undefined
attributes as null
attributes. This means that if the document in the database does not contain an attribute for a nullable property, DynaMap
will create that attribute automatically.
@Serializable
data class Book(val name: String, val author: String?, val price: Int)
val itemOnDatabase = mapOf(
"name" to AttributeValue.S("Harry Potter"),
"price" to AttributeValue.N("10")
)
// Will deserialize object successfully.
val obj1 = Dynamap { evaluateUndefinedAttributesAsNullAttribute = true }.decodeFromItem<Book>(itemOnDatabase)
// Will raise DynamapSerializationException.UnexpectedUndefined exception.
val obj2 = Dynamap { evaluateUndefinedAttributesAsNullAttribute = false }.decodeFromItem<Book>(itemOnDatabase)
DynaMap
can override the name used in encoded document using @SerialName
annotation.
@Serializable
data class Book(
@SerialName("BookName")
val name: String,
val author: String?,
val price: Int
)
val book = Book("Harry Potter", "JKRowling", 10)
val item = Dynamap.encodeToItem(book)
println(item) // {BookName=S(value=Harry Potter), author=S(value=JKRowling), price=N(value=10)}
kotlinx.serialization
manages ByteArray
internally as a list of bytes. So, if we try to serialize / deserialize it with default serializer, it will convert the ByteArray
to a list of numbers in DynamoDB.
@Serializable
data class User(val username: String, val passwordHash: ByteArray)
val user = User(username = "Codanbaru", passwordHash = "AQIDBA==".decodeBase64Bytes())
val item: Map<String, AttributeValue> = Dynamap.encodeToItem(user)
println(item) // {username=S(value=Codanbaru), passwordHash=L(value=[N(value=1), N(value=2), N(value=3), N(value=4)])}
To convert ByteArray
to Binary type on DynamoDB, you can use DynamoBinarySerializer
.
@Serializable
data class User(
val username: String,
@Serializable(with = DynamoBinarySerializer::class)
val passwordHash: ByteArray
)
val user = User(username = "Codanbaru", passwordHash = "AQIDBA==".decodeBase64Bytes())
val item: Map<String, AttributeValue> = Dynamap.encodeToItem(user)
println(item) // {username=S(value=Codanbaru), passwordHash=B(value=[1, 2, 3, 4])}
DynaMap
also supports serialization / deserialization of polyphormic structures1.
At the moment, Dynamap
currently support Closed polymorphism
only.
- ✅ Closed polymorphism
- ❌ Open polymorphism
1 You can get more information about polymorphism on kotlinx.serialization
documentation.
val dynamap = Dynamap { }
@Serializable
sealed class Social {
@Serializable
data class Email(
var email: String
) : Social()
@Serializable
data class Phone(
val country: String,
val number: String
) : Social()
@Serializable
data class Instagram(
val username: String
) : Social()
}
@Serializable
data class User(val name: String, val social: Social)
val user0 = User(name = "Codanbaru 0", social = Social.Email("demo@codanbaru.com"))
val user1 = User(name = "Codanbaru 0", social = Social.Instagram("@codanbaru"))
val item0: Map<String, AttributeValue> = dynamap.encodeToItem(user0)
val item1: Map<String, AttributeValue> = dynamap.encodeToItem(user1)
println(item0) // {name=S(value=Codanbaru 0), social=M(value={email=S(value=demo@codanbaru.com), __dynamap_serialization_type=S(value=com.codanbaru.app.Social.Email)})}
println(item1) // {name=S(value=Codanbaru 0), social=M(value={username=S(value=@codanbaru), __dynamap_serialization_type=S(value=com.codanbaru.app.Social.Instagram)})}
You can specify, at Dynamap
creation, which key kotlinx.serialization
should use to discriminate classes.
val dynamap = Dynamap {
classDiscriminator = "type"
}
// ...
println(item0) // {name=S(value=Codanbaru 0), social=M(value={email=S(value=demo@codanbaru.com), type=S(value=com.codanbaru.app.Social.Email)})}
println(item1) // {name=S(value=Codanbaru 0), social=M(value={username=S(value=@codanbaru), type=S(value=com.codanbaru.app.Social.Instagram)})}
Also, with @SerialName
annotation, you can override the class name used by kotlinx.serialization
and Dynamap
.
@Serializable
sealed class Social {
@Serializable
@SerialName("email")
data class Email(
var email: String
) : Social()
@Serializable
@SerialName("phone")
data class Phone(
val country: String,
val number: String
) : Social()
@Serializable
@SerialName("instagram")
data class Instagram(
val username: String
) : Social()
}
// ...
println(item0) // {name=S(value=Codanbaru 0), social=M(value={email=S(value=demo@codanbaru.com), type=S(value=com.codanbaru.app.Social.Email)})}
println(item1) // {name=S(value=Codanbaru 0), social=M(value={username=S(value=@codanbaru), type=S(value=com.codanbaru.app.Social.Instagram)})}