BSON format for kotlinx.serialization. Serialize and deserialize documents from BSON using kotlinx.serialization
. This library provides a straightforward and efficient way to convert BsonDocument objects into domain-specific data types in Kotlin. Whether you’re building a server-side application, integrating with MongoDB, or handling BSON data structures, this library makes the transformation process seamless and type-safe.
According with Wikipedia, BSON is a computer data interchange format. It is a binary form for representing simple or complex data structures including associative arrays, integer indexed arrays, and a suite of fundamental scalar types.
If you are using the native MongoDB driver to interact with MongoDB databases, you are likely working with BSON values.
Although the native MongoDB driver for Kotlin already provides support for serializing and deserializing data objects to and from BSON, there are situations where you may need to work directly with BsonDocuments before converting them into native Kotlin entities. This library is designed specifically to address such use cases.
To utilize the BSONMap library, you will need to perform serialization and deserialization in a manner similar to working with kotlinx.serialization.
- Install
kotlinx.serialization
plugin. - Add
BSONMap
serialization dependency.
Kotlin DSL:
plugins {
kotlin("jvm")
// ADD SERIALIZATION PLUGIN
kotlin("plugin.serialization")
}
dependencies {
// ADD SERIALIZATION DEPENDENCY
implementation("com.codanbaru.kotlin:bsonmap:0.9.0")
}
Groovy DSL
plugins {
// ADD SERIALIZATION PLUGIN
id 'org.jetbrains.kotlin.plugin.serialization'
}
dependencies {
// ADD SERIALIZATION DEPENDENCY
implementation 'com.codanbaru.kotlin:bsonmap:0.9.0'
}
@Serializable
data class Book(val name: String, val author: String?)
val bsonmap = Bsonmap {
evaluateUndefinedAttributesAsNullAttribute = false
}
fun encodeBook(book: Book): BsonDocument {
val document: BsonDocument = bsonmap.encodeToDocument(book)
return document
}
fun decodeBook(document: BsonDocument): Book {
val obj: Book = bsonmap.decodeFromDocument(document)
return obj
}
By default, the BSONMap
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, BSONMap
will create that attribute automatically.
@Serializable
data class Book(val name: String, val author: String?)
val document: BsonDocument = BsonDocument()
.append(
"name",
BsonString("Harry Potter")
)
// Will deserialize object successfully.
val book1: Book = Bsonmap {
evaluateUndefinedAttributesAsNullAttribute = true
}.decodeFromDocument(document)
// Will raise BsonmapSerializationException.UnexpectedUndefined exception.
val book2: Book = Bsonmap {
evaluateUndefinedAttributesAsNullAttribute = false
}.decodeFromDocument(document)
BSONMap
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 document = Bsonmap.encodeToDocument(book)
println(document) // {"BookName": "Harry Potter", "author": "JKRowling", "price": 10}
kotlinx.serialization
handles ByteArray
internally as a list of bytes. Consequently, when serializing or deserializing a ByteArray
using the default serializer, it will be transformed into a list of BsonInt32 elements in BSON format.
@Serializable
data class User(val username: String, val passwordHash: ByteArray
val user = User(username = "Codanbaru", passwordHash = "AQIDBA==".decodeBase64Bytes())
val document: BsonDocument = Bsonmap.encodeToDocument(user
println(document) // {"username": "Codanbaru", "passwordHash": [1, 2, 3, 4]}
However, in BSON, it is strongly recommended to store binary data using BsonBinary elements. To facilitate this, the library provides a custom serializer called BsonMapBinarySerializer.
@Serializable
data class User(
val username: String,
@Serializable(with = BsonmapBinarySerializer::class)
val passwordHash: ByteArray
)
val user = User(username = "Codanbaru", passwordHash = "AQIDBA==".decodeBase64Bytes())
val document: BsonDocument = Bsonmap.encodeToDocument(user
println(document) // {"username": "Codanbaru", "passwordHash": {"$binary": {"base64": "AQIDBA==", "subType": "00"}}}
BSONMap
also supports serialization / deserialization of polyphormic structures1.
At the moment, Bsonmap
currently support Closed polymorphism
only.
- ✅ Closed polymorphism
- ❌ Open polymorphism
1 You can get more information about polymorphism on kotlinx.serialization
documentation.
val bsonmap = Bsonmap { }
@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 document0: BsonDocument = bsonmap.encodeToDocument(user0)
val document1: BsonDocument = bsonmap.encodeToDocument(user1)
println(document0) // {"name": "Codanbaru 0", "social": {"email": "demo@codanbaru.com", "__bsonmap_serialization_type": "com.codanbaru.entity.Social.Email"}}
println(document1) // {"name": "Codanbaru 0", "social": {"username": "@codanbaru", "__bsonmap_serialization_type": "com.codanbaru.entity.Social.Instagram"}}
You can specify, at Bsonmap
creation, which key kotlinx.serialization
should use to discriminate classes.
val bsonmap = Bsonmap {
classDiscriminator = "type"
}
// ...
println(document0) // {"name": "Codanbaru 0", "social": {"email": "demo@codanbaru.com", "type": "com.codanbaru.entity.Social.Email"}}
println(document1) // {"name": "Codanbaru 0", "social": {"username": "@codanbaru", "type": "com.codanbaru.entity.Social.Instagram"}}
Also, with @SerialName
annotation, you can override the class name used by kotlinx.serialization
and Bsonmap
.
@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(document0) // {"name": "Codanbaru 0", "social": {"email": "demo@codanbaru.com", "__bsonmap_serialization_type": "email"}}
println(document1) // {"name": "Codanbaru 0", "social": {"username": "@codanbaru", "__bsonmap_serialization_type": "instagram"}}