In this guide, you can learn about Codecs and the supporting classes that handle the encoding and decoding of Kotlin objects to and from BSON data in the MongoDB Kotlin driver. The Codec
abstraction allows you to map any Kotlin type to a corresponding BSON type. You can use this to map your domain objects directly to and from BSON instead of using data classes or an intermediate map-based object such as Document
or BsonDocument
.
You can learn how to specify custom encoding and decoding logic using the Codec
abstraction and view example implementations in the following sections:
The Codec
interface contains abstract methods for serializing and deserializing Kotlin objects to BSON data. You can define your conversion logic between BSON and your Kotlin object in your implementation of this interface.
To implement the Codec
interface, override the encode()
, decode()
, and getEncoderClass()
abstract methods.
The encode()
method requires the following parameters:
Parameter Type
Description
writer
An instance of a class that implements BsonWriter
, an interface type that exposes methods for writing a BSON document. For example, the BsonBinaryWriter
implementation writes to a binary stream of data. Use this instance to write your BSON value using the appropriate write method.
value
The data that your implementation encodes. The type must match the type variable assigned to your implementation.
encoderContext
Contains meta information about the Kotlin object data that it encodes to BSON including whether to store the current value in a MongoDB collection.
This method uses the BsonWriter
instance to send the encoded value to MongoDB and does not return a value.
The decode()
method returns your Kotlin object instance populated with the value from the BSON data. This method requires the following parameters:
Parameter Type
Description
bsonReader
An instance of a class that implements BsonReader
, an interface type that exposes methods for reading a BSON document. For example, the BsonBinaryReader
implementation reads from a binary stream of data.
decoderContext
Contains information about the BSON data that it decodes to a Kotlin object.
The getEncoderClass()
method returns a class instance of the Kotlin class since Kotlin cannot infer the type due to type erasure.
See the following code examples that show how you can implement a custom Codec
.
The PowerStatus
enum contains the values "ON" and "OFF" to represent the states of an electrical switch.
enum class PowerStatus { ON, OFF}
The PowerStatusCodec
class implements Codec
in order to convert the Kotlin enum
values to corresponding BSON boolean values. The encode()
method converts a PowerStatus
to a BSON boolean and the decode()
method performs the conversion in the opposite direction.
class PowerStatusCodec : Codec<PowerStatus> { override fun encode(writer: BsonWriter, value: PowerStatus, encoderContext: EncoderContext) = writer.writeBoolean(value == PowerStatus.ON) override fun decode(reader: BsonReader, decoderContext: DecoderContext): PowerStatus { return when (reader.readBoolean()) { true -> PowerStatus.ON false -> PowerStatus.OFF } } override fun getEncoderClass(): Class<PowerStatus> = PowerStatus::class.java}
You can add an instance of the PowerStatusCodec
to your CodecRegistry
which contains a mapping between your Codec
and the Kotlin object type to which it applies. Continue to the CodecRegistry section of this page to see how you can include your Codec
.
For more information about the classes and interfaces in this section, see the following API Documentation:
A CodecRegistry
is an immutable collection of Codec
instances that encode and decode the Kotlin classes they specify. You can use any of the following CodecRegistries
class static factory methods to construct a CodecRegistry
from the Codec
instances contained in the associated types:
fromCodecs()
fromProviders()
fromRegistries()
The following code snippet shows how to construct a CodecRegistry
using the fromCodecs()
method:
val codecRegistry = CodecRegistries.fromCodecs(IntegerCodec(), PowerStatusCodec())
In the preceding example, we assign the CodecRegistry
the following Codec
implementations:
IntegerCodec
, a Codec
that converts Integers
and is part of the BSON package.
PowerStatusCodec, our sample Codec
that converts Kotlin enum values to BSON booleans.
You can retrieve the Codec
instances from the CodecRegistry
instance from the prior example using the following code:
val powerStatusCodec = codecRegistry.get(PowerStatus::class.java)val integerCodec = codecRegistry.get(Integer::class.java)
If you attempt to retrieve a Codec
instance for a class that is not registered, the get()
method throws a CodecConfigurationException
exception.
For more information about the classes and interfaces in this section, see the following API Documentation:
A CodecProvider
is an interface that contains abstract methods that create Codec
instances and assign them to a CodecRegistry
instance. Similar to the CodecRegistry
, the BSON library uses the Codec
instances retrieved by the get()
method to convert between Kotlin and BSON data types.
However, in cases in which you add a class that contains fields that require corresponding Codec
objects, you need to ensure that you instantiate the Codec
objects for the class' fields before you instantiate the Codec
for the class. You can use the CodecRegistry
parameter in the get()
method to pass any of the Codec
instances that the Codec
relies on.
The following code example shows how you can implement CodecProvider
to pass the MonolightCodec
any Codec
instances it needs in a CodecRegistry
instance such as the PowerStatusCodec
from our prior example:
class MonolightCodec(registry: CodecRegistry) : Codec<Monolight> { private val powerStatusCodec: Codec<PowerStatus> private val integerCodec: Codec<Int> init { powerStatusCodec = registry[PowerStatus::class.java] integerCodec = IntegerCodec() } override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) { writer.writeStartDocument() writer.writeName("powerStatus") powerStatusCodec.encode(writer, value.powerStatus, encoderContext) writer.writeName("colorTemperature") integerCodec.encode(writer, value.colorTemperature, encoderContext) writer.writeEndDocument() } override fun decode(reader: BsonReader, decoderContext: DecoderContext): Monolight { val monolight = Monolight() reader.readStartDocument() while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { when (reader.readName()) { "powerStatus" -> monolight.powerStatus = powerStatusCodec.decode(reader, decoderContext) "colorTemperature" -> monolight.colorTemperature = integerCodec.decode(reader, decoderContext) "_id" -> reader.readObjectId() } } reader.readEndDocument() return monolight } override fun getEncoderClass(): Class<Monolight> = Monolight::class.java}
To see a runnable example that demonstrates read and write operations using these Codec
classes, see the Custom Codec Example section of this guide.
The default codec registry is a set of CodecProvider
classes that specify conversion between commonly-used Kotlin and MongoDB types. The driver automatically uses the default codec registry unless you specify a different one.
If you need to override the behavior of one or more Codec
classes, but keep the behavior from the default codec registry for the other classes, you can specify all of the registries in order of precedence. For example, suppose you wanted to override the default provider behavior of a Codec
for enum types with your custom MyEnumCodec
, you must add it to the registry list prior to the default codec registry as shown in the example below:
val newRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(MyEnumCodec()), MongoClientSettings.getDefaultCodecRegistry())
For more information about the classes and interfaces in this section, see the following API documentation sections:
The BsonTypeClassMap
class contains a recommended mapping between BSON and Kotlin types. You can use this class in your custom Codec
or CodecProvider
to help you manage which Kotlin types to decode your BSON types to container classes that implement Iterable
or Map
such as the Document
class.
You can add or modify the BsonTypeClassMap
default mapping by passing a Map
containing new or replacement entries.
The following code snippet shows how you can retrieve the Kotlin class type that corresponds to the BSON type in the default BsonTypeClassMap
instance:
val bsonTypeClassMap = BsonTypeClassMap()val clazz = bsonTypeClassMap[BsonType.ARRAY]println("Class name: " + clazz.name)
Java type: java.util.List
You can modify these mappings in your instance by specifying replacements in the BsonTypeClassMap
constructor. The following code snippet shows how you can replace the mapping for ARRAY
in your BsonTypeClassMap
instance with the Set
class:
val replacements = mutableMapOf<BsonType, Class<*>>(BsonType.ARRAY to MutableSet::class.java)val bsonTypeClassMap = BsonTypeClassMap(replacements)val clazz = bsonTypeClassMap[BsonType.ARRAY]println("Class name: " + clazz.name)
For a complete list of the default mappings, refer to the BsonTypeClassMap API Documentation.
For an example of how the Document
class uses BsonTypeClassMap
, see the driver source code for the following classes:
In this section, we show how you can implement Codec
and CodecProvider
to define the encoding and decoding logic for a custom Kotlin class. We also show how you can specify and use your custom implementations to perform insert and retrieve operations.
As an alternative to implementing custom codecs, you can use Kotlin serialization to handle your data encoding and decoding with @Serializable
classes. You might choose Kotlin serialization if you are already familiar with the framework or prefer to use an idiomatic Kotlin approach. See the Kotlin Serialization documentation for more information.
The following code snippet shows our example custom class called Monolight
and its fields that we want to store and retrieve from a MongoDB collection:
data class Monolight( var powerStatus: PowerStatus = PowerStatus.OFF, var colorTemperature: Int? = null) { override fun toString(): String = "Monolight [powerStatus=$powerStatus, colorTemperature=$colorTemperature]"}
This class contains the following fields, each of which we need to assign a Codec
:
powerStatus
describes whether the light is switched "on" or "off" for which we use the PowerStatusCodec that converts specific enum values to BSON booleans.
colorTemperature
describes the color of the light and contains an Int
value for which we use the IntegerCodec
included in the BSON library.
The following code example shows how we can implement a Codec
for the Monolight
class. Note that the constructor expects an instance of CodecRegistry
from which it retrieves the Codec
instances it needs to encode and decode its fields:
class MonolightCodec(registry: CodecRegistry) : Codec<Monolight> { private val powerStatusCodec: Codec<PowerStatus> private val integerCodec: Codec<Int> init { powerStatusCodec = registry[PowerStatus::class.java] integerCodec = IntegerCodec() } override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) { writer.writeStartDocument() writer.writeName("powerStatus") powerStatusCodec.encode(writer, value.powerStatus, encoderContext) writer.writeName("colorTemperature") integerCodec.encode(writer, value.colorTemperature, encoderContext) writer.writeEndDocument() } override fun decode(reader: BsonReader, decoderContext: DecoderContext): Monolight { val monolight = Monolight() reader.readStartDocument() while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { when (reader.readName()) { "powerStatus" -> monolight.powerStatus = powerStatusCodec.decode(reader, decoderContext) "colorTemperature" -> monolight.colorTemperature = integerCodec.decode(reader, decoderContext) "_id" -> reader.readObjectId() } } reader.readEndDocument() return monolight } override fun getEncoderClass(): Class<Monolight> = Monolight::class.java}
To ensure we make the Codec
instances for the fields available for Monolight
, we implement a custom CodecProvider
shown in the following code example:
class MonolightCodecProvider : CodecProvider { @Suppress("UNCHECKED_CAST") override fun <T> get(clazz: Class<T>, registry: CodecRegistry): Codec<T>? { return if (clazz == Monolight::class.java) { MonolightCodec(registry) as Codec<T> } else null }}
After defining the conversion logic, we can perform the following:
Store data from instances of Monolight
into MongoDB
Retrieve data from MongoDB into instances of Monolight
The following example class contains code that assigns the MonolightCodecProvider
to the MongoCollection
instance by passing it to the withCodecRegistry()
method. The example class also inserts and retrieves data using the Monolight
class and associated codecs:
fun main() = runBlocking { val mongoClient = MongoClient.create("<connection string uri>") val codecRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(IntegerCodec(), PowerStatusCodec()), CodecRegistries.fromProviders(MonolightCodecProvider()), MongoClientSettings.getDefaultCodecRegistry() ) val database = mongoClient.getDatabase("codecs_example_products") val collection = database.getCollection<Monolight>("monolights") .withCodecRegistry(codecRegistry) val myMonolight = Monolight(PowerStatus.ON, 5200) collection.insertOne(myMonolight) val lights = collection.find().toList() println(lights)}
[Monolight [powerStatus=ON, colorTemperature=5200]]
For more information about the methods and classes mentioned in this section, see the following API Documentation:
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4