Overview
In this guide, you can learn about codecs and the supporting classes that handle the encoding and decoding of Scala objects to and from BSON data in the Scala driver. The Codec abstraction allows you to map any Scala type to a corresponding BSON type. Use this to map your domain objects directly to and from BSON instead of relying on an intermediate map-based object such as Document or BsonDocument.
Codec Interface
The Codec interface contains abstract methods for encoding and decoding Scala objects to and from BSON data. Implement these methods to define the conversion logic between BSON and your Codec implementation's Scala type.
To implement the Codec interface, define the interface's encode(), decode(), and getEncoderClass() methods. To see a code example that implements these methods, see the Basic Custom Codec Example section.
encode() Method
The encode() method encodes an instance of the Scala type to BSON so that the driver can store it in MongoDB. This method requires the following parameters:
Parameter Type | Description |
|---|---|
| An instance of a class that implements |
| The data that the method encodes. The |
| Metadata about the Scala object that the method encodes to BSON, including whether to store the current value in a MongoDB collection. |
The encode() method doesn't return a value.
decode() Method
The decode() method uses the BSON data to decode an instance of the Scala type. This method requires the following parameters:
Parameter Type | Description |
|---|---|
| An instance of a class that implements |
| Metadata about the BSON data that the method decodes to a Scala object. |
getEncoderClass() Method
The getEncoderClass() method returns an instance of the Scala type defined in the Codec. This method provides the type information that the Java Virtual Machine (JVM) erases at runtime.
Basic Custom Codec Example
The following code samples use the PowerStatus sealed trait and PowerStatusCodec class to show how you can implement a custom Codec.
The PowerStatus sealed trait uses the case objects On and Off to represent the states of an electrical switch:
sealed trait PowerStatus object PowerStatus { case object On extends PowerStatus case object Off extends PowerStatus }
The PowerStatusCodec class implements the Codec interface to encode PowerStatus values to corresponding BSON Boolean values. The encode() method encodes a PowerStatus value to a BSON Boolean value and the decode() method decodes a BSON Boolean value to a PowerStatus value.
class PowerStatusCodec extends Codec[PowerStatus] { override def encode(writer: BsonWriter, value: PowerStatus, encoderContext: EncoderContext): Unit = { if (value != null) { writer.writeBoolean(value == PowerStatus.On) } } override def decode(reader: BsonReader, decoderContext: DecoderContext): PowerStatus = { if (reader.readBoolean()) PowerStatus.On else PowerStatus.Off } override def getEncoderClass: Class[PowerStatus] = classOf[PowerStatus] }
To use the PowerStatusCodec class, you must add an instance of the class to your CodecRegistry interface, which maps your Codec to the corresponding Scala type. The driver can encode and decode a type only if the CodecRegistry contains a Codec for that type. To learn how to include your custom Codec in your CodecRegistry interface, see the CodecRegistry section of this page.
For more information about the classes and interfaces in this section, see the following API documentation:
CodecRegistry Collection
A CodecRegistry is an immutable collection of Codec instances. To construct a CodecRegistry instance, use one of the following CodecRegistries class static factory methods. Each method builds the registry from a different source of Codec instances:
Method | Description |
|---|---|
| Builds a registry from the |
| Builds a registry from the |
| Builds a registry by combining the other |
The following examples use two Codec implementations:
IntegerCodec: ACodecin the BSON package that encodes JavaIntegervalues to BSON 32-bit integer values.PowerStatusCodec: A sample
Codecthat decodesPowerStatusvalues to BSON Booleans.
The following example shows how to construct a CodecRegistry instance by using the fromCodecs() method to assign these implementations to the registry:
val codecRegistry = CodecRegistries.fromCodecs( new IntegerCodec(), new PowerStatusCodec() )
The following example retrieves the Codec instances in the previous example from the CodecRegistry collection:
val powerStatusCodec: Codec[PowerStatus] = codecRegistry.get(classOf[PowerStatus]) val integerCodec: Codec[Integer] = codecRegistry.get(classOf[Integer])
Note
If you call the get() method on a Codec instance for an unregistered class, the driver throws a CodecConfigurationException.
Default Codec Registry
The default codec registry is a set of CodecProvider classes that encode between commonly used Scala and MongoDB types. The driver automatically uses the default codec registry unless you specify a custom codec registry. To learn more about the CodecProvider interface, see the CodecProvider section.
If you must override the behavior of one or more Codec classes, but want to 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, to override the default provider behavior of a Codec for a custom type with your MyEnumCodec, add it to the registry list before the default codec registry. The following example shows this pattern:
val newRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(new MyEnumCodec()), MongoClientSettings.getDefaultCodecRegistry() )
For more information about the classes and interfaces in this section, see the following API documentation:
CodecProvider Interface
The CodecProvider interface contains abstract methods that create Codec instances and assign them to a CodecRegistry instance. Like the CodecRegistry interface, the CodecProvider interface defines a get() method that returns Codec instances. The BSON library uses these Codec instances to encode between Scala and BSON data types.
Use a CodecProvider when you add a class to your code whose fields need corresponding Codec objects. When each field requires its own Codec instance, you must instantiate the Codec objects for each field before instantiating the Codec instance for the class. Use the CodecRegistry parameter in the get() method to pass any of the Codec instances that the Codec relies on into a constructor.
The following example shows how to implement a CodecProvider interface. The driver uses the implemented MonolightCodecProvider interface to create a MonolightCodec instance for the Monolight class.
class MonolightCodecProvider extends CodecProvider { override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] = { if (clazz == classOf[Monolight]) { new MonolightCodec(registry).asInstanceOf[Codec[T]] } else { null } } }
For a complete implementation of a CodecProvider interface, including custom classes, see the Complete Custom Codec Example section of this guide.
For more information about the classes and interfaces in this section, see the following API documentation:
Complete Custom Codec Example
In this section, you can learn how to implement the Codec and CodecProvider interfaces to define the encoding and decoding logic for a custom Scala class. This section also shows how to specify and use your custom implementations to perform insert and retrieve operations.
The following code snippet shows the example custom class Monolight and its fields:
case class Monolight( powerStatus: PowerStatus = PowerStatus.Off, colorTemperature: Int = 0 )
The Monolight class contains the following fields, each of which requires a Codec interface implementation:
powerStatusdescribes if the light is switched on or off. The PowerStatusCodec class encodesPowerStatusvalues to BSON Booleans.colorTemperaturedescribes the color of the light and contains anIntvalue. TheIntegerCodecclass included in the BSON library encodescolorTemperaturevalues to BSON 32-bit integers.
The following code example shows how to implement a Codec interface for the Monolight class. The constructor uses a CodecRegistry instance to retrieve the Codec instances it needs to encode and decode the Monolight fields.
class MonolightCodec(registry: CodecRegistry) extends Codec[Monolight] { private val powerStatusCodec: Codec[PowerStatus] = registry.get(classOf[PowerStatus]) private val integerCodec: Codec[Integer] = registry.get(classOf[Integer]) override def encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext): Unit = { writer.writeStartDocument() writer.writeName("powerStatus") powerStatusCodec.encode(writer, value.powerStatus, encoderContext) writer.writeName("colorTemperature") integerCodec.encode(writer, value.colorTemperature, encoderContext) writer.writeEndDocument() } override def decode(reader: BsonReader, decoderContext: DecoderContext): Monolight = { var powerStatus: PowerStatus = PowerStatus.Off var colorTemperature: Int = 0 reader.readStartDocument() while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { reader.readName() match { case "powerStatus" => powerStatus = powerStatusCodec.decode(reader, decoderContext) case "colorTemperature" => colorTemperature = integerCodec.decode(reader, decoderContext) case "_id" => reader.readObjectId() case _ => reader.skipValue() } } reader.readEndDocument() Monolight(powerStatus, colorTemperature) } override def getEncoderClass: Class[Monolight] = classOf[Monolight] }
The following code example shows how to construct Codec instances for the fields in the Monolight class and implement a custom CodecProvider:
class MonolightCodecProvider extends CodecProvider { override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] = { if (clazz == classOf[Monolight]) { new MonolightCodec(registry).asInstanceOf[Codec[T]] } else { null } } }
The get() method returns a new MonolightCodec when the driver requests a Codec for the Monolight class. The method passes the CodecRegistry to the MonolightCodec constructor so the MonolightCodec can retrieve the Codec instances for its fields, such as PowerStatusCodec and IntegerCodec. If the driver requests a Codec for any other class, the method returns null.
After you define the conversion logic, you can perform the following operations:
Store data from instances of the
Monolightclass into MongoDBRetrieve data from MongoDB into instances of the
Monolightclass
The following example assigns the MonolightCodecProvider class to the MongoCollection instance by passing it to the withCodecRegistry() method. The example then inserts a new Monolight instance into the collection by calling the insertOne() method and then calling the find() method to return the stored Monolight instance. The output shows the retrieved Monolight instance, which confirms that the custom codecs successfully encoded and decoded the data.
object MonolightCodecExample { def main(args: Array[String]): Unit = { val uri = "<connection string URI>" val mongoClient = MongoClient(uri) val codecRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs( new IntegerCodec(), new PowerStatusCodec() ), CodecRegistries.fromProviders(new MonolightCodecProvider()), MongoClientSettings.getDefaultCodecRegistry() ) val database = mongoClient.getDatabase("codecs_example_products") val collection: MongoCollection[Monolight] = database .getCollection[Monolight]("monolights") .withCodecRegistry(codecRegistry) val myMonolight = Monolight(PowerStatus.On, 5200) Await.result( collection.insertOne(myMonolight).toFuture(), Duration.Inf ) val lights = Await.result( collection.find().toFuture(), Duration.Inf ) println(lights) mongoClient.close() } }
List(Monolight(On,5200))
For more information about the methods and classes mentioned in this section, see the following API documentation: