Codecs
On this page
Overview
In this guide, you can learn about Codecs and supporting classes that
handle the encoding and decoding of Kotlin objects to and from
BSON data. The Codec
abstraction allows you to map any
Kotlin type to a corresponding BSON type. You can use this feature to map
your domain objects directly to and from BSON instead of using classes
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:
Tip
Kotlin Serialization
You can use Kotlin serialization to handle your data encoding and decoding with
@Serializable
classes instead of implementing custom codecs. You
might choose Kotlin serialization if you are already familiar
with the kotlinx.serialization
library or prefer to
use an idiomatic Kotlin approach. To learn more, see the
Kotlin Serialization guide.
Codec
The Codec
interface contains abstract methods for serializing and
deserializing Kotlin objects to BSON data. You can define custom
conversion logic in your implementation of this interface.
To implement the Codec
interface, override the encode()
, decode()
,
and getEncoderClass()
abstract methods.
encode() Method
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. |
The encode()
method uses the BsonWriter
instance to send the
encoded value to MongoDB and does not return a value.
decode() Method
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 |
---|---|
reader | 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.
Examples
This section contains code examples that show how you can implement a
custom Codec
interface.
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 the Codec
interface to convert
the Kotlin enum
values to corresponding BSON boolean values. The
encode()
method converts a PowerStatus
value 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 PowerStatusCodec
to your
CodecRegistry
. View the CodecRegistry section of this page to learn how to include
your Codec
in your registry.
To learn more about the classes and interfaces mentioned in this section, see the following API documentation:
CodecRegistry
A CodecRegistry
is an immutable collection of Codec
instances that
encode and decode Kotlin classes. 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()
: Creates a registry fromCodec
instancesfromProviders()
: Creates a registry fromCodecProvider
instancesfromRegistries()
: Creates a registry fromCodecRegistry
instances
The following code shows how to construct a CodecRegistry
by using
the fromCodecs()
method:
val codecRegistry = CodecRegistries .fromCodecs(IntegerCodec(), PowerStatusCodec())
The preceding example assigns the CodecRegistry
the following Codec
implementations:
IntegerCodec
:Codec
that convertsIntegers
. It is part of the BSON package.PowerStatusCodec
: SampleCodec
from the preceding section that converts Kotlin enum values to BSON booleans.
You can retrieve the Codec
instances from the CodecRegistry
instance
by 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 codecRegistry.get()
method raises a
CodecConfigurationException
exception.
For more information about the classes and interfaces in this section, see the following API documentation:
CodecProvider
A CodecProvider
is an interface that contains abstract methods to create
Codec
instances and assign them to CodecRegistry
instances. Similar
to the CodecRegistry
interface, the BSON library uses the Codec
instances
retrieved by the CodecProvider.get()
method to convert between
Kotlin and BSON data types.
However, in cases that you add a class that contains fields requiring
corresponding Codec
objects, ensure that you instantiate the
Codec
objects for the class' fields before you instantiate the
Codec
for the entire class. You can use the CodecRegistry
parameter in
the CodecProvider.get()
method to pass any of the Codec
instances that the Codec
relies on.
To see a runnable example that demonstrates read and write operations
using Codec
classes, see the Custom Codec Example
section of this guide.
Default Codec Registry
The default codec registry is a set of CodecProvider
classes that
specify conversion between commonly-used Kotlin objects and
MongoDB types. The driver automatically uses the default codec registry
unless you specify a different one.
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 the registries in order of precedence. For example,
suppose you want to override the default provider behavior of a Codec
for
enum types with your custom MyEnumCodec
. You must add it to the registry
list in a position before the default codec registry as shown in the
following example:
val newRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(MyEnumCodec()), MongoClientSettings.getCodecRegistry() )
For more information about the classes and interfaces in this section, see the following API documentation:
BsonTypeClassMap
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. It also contains 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 shows how to retrieve the Kotlin class type
that corresponds to the BSON array type in the default BsonTypeClassMap
instance:
val bsonTypeClassMap = BsonTypeClassMap() val clazz = bsonTypeClassMap[BsonType.ARRAY] println("Kotlin class name: " + clazz.name)
Kotlin class name: 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 the BSON array type 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)
Kotlin class name: java.util.Set
For a complete list of the default mappings, view the BsonTypeClassMap API documentation.
Custom Codec Example
This section demonstrates how you can implement Codec
and
CodecProvider
interfaces to define the encoding and decoding logic
for a custom Kotlin class. It shows how you can specify and use
your custom implementations to perform read and write operations.
The following code defines the sample data class Monolight
:
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 that each require a
corresponding Codec
to handle encoding and decoding:
powerStatus
: Describes whether the device light is"ON"
or"OFF"
. For this field, use the PowerStatusCodec which converts thePowerStatus
enum values to BSON booleans.colorTemperature
: Describes the color of the device light in kelvins as anInt
value. For this field, use theIntegerCodec
provided in the BSON library.
The following code shows how to can implement a Codec
for the
Monolight
class. The constructor expects an instance of
CodecRegistry
from which it retrieves the Codec
instances needed
to encode and decode the class 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 that the Codec
instances for the fields are available for
the Monolight
class, implement a custom CodecProvider
as shown
in the following code example:
class MonolightCodecProvider : CodecProvider { override fun <T> get(clazz: Class<T>, registry: CodecRegistry): Codec<T>? { return if (clazz == Monolight::class.java) { MonolightCodec(registry) as Codec<T> } else null // Return null when not a provider for the requested class } }
After defining the conversion logic, you can perform the following actions:
Store instances of
Monolight
in MongoDBRetrieve documents from MongoDB as instances of
Monolight
The following code assigns the MonolightCodecProvider
to the
MongoCollection
instance by passing it to the
withCodecRegistry()
method. The example class also inserts and
retrieves data by using the Monolight
class:
val mongoClient = MongoClient.create(uri) val codecRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(IntegerCodec(), PowerStatusCodec()), CodecRegistries.fromProviders(MonolightCodecProvider()), MongoClientSettings.getDefaultCodecRegistry() ) val database = mongoClient.getDatabase("codec_test") val collection = database.getCollection<Monolight>("monolights") .withCodecRegistry(codecRegistry) // Insert instances of Monolight val monolights = listOf( Monolight(PowerStatus.ON, 5200), Monolight(PowerStatus.OFF, 3000) ) collection.insertMany(monolights) // Retrieve instances of Monolight val results = collection.find() results.forEach { l -> println(l) }
Monolight { powerStatus: ON, colorTemperature: 5200 } Monolight { powerStatus: OFF, colorTemperature: 3000 }
For more information about the methods and classes mentioned in this section, see the following API documentation: