Macros

New in 2.0, the Scala driver allows you to use case classes to represent documents in a collection via the Macros helper. Simple case classes and nested case classes are supported. Hierarchical modelling can be achieve by using a sealed trait and having case classes implement the parent trait.

Many simple Scala types are supported and they will be marshaled into their corresponding BsonValue type. Below is a list of Scala types and their type-safe BSON representation:

Scala type BSON type
case class Document
Iterable Array
Date Date
Boolean Boolean
Double Double
Int Int32
Long Int64
String String
Array[Byte] Binary
None Null

Creating Codecs

To create a codec for your case class use the Macros object helper methods. Unless there is a good reason you should use the Macros.createCodecProvider method to create a CodecProvider. A CodecProvider will pass the configured CodecRegistry to the underlying Codec and provide access to all the configured codecs.

To create a CodecProvider all you need to do is to set the case class type when calling createCodecProvider like so:

import org.mongodb.scala.bson.codecs.Macros

case class Person(firstName: String, secondName: String)

val personCodecProvider = Macros.createCodecProvider[Person]()

The personCodecProvider can then be used when converted into a CodecRegistry by using the CodecRegistries static helpers. Below we create a new codec registry combining the new personCodecProvider and the the default codec registry:

import org.mongodb.scala.bson.codecs.DEFAULT_CODEC_REGISTRY
import org.bson.codecs.configuration.CodecRegistries.{fromRegistries, fromProviders}

val codecRegistry = fromRegistries( fromProviders(personCodecProvider), DEFAULT_CODEC_REGISTRY )

The Macros helper also has an implicit createCodecProvider method that takes the Class[T] and will create a CodecProvider from that. As you can see in the example below it’s much more concise especially when defining multiple providers:

import org.mongodb.scala.bson.codecs.Macros._
import org.mongodb.scala.bson.codecs.DEFAULT_CODEC_REGISTRY
import org.bson.codecs.configuration.CodecRegistries.{fromRegistries, fromProviders}

case class Address(firstLine: String, secondLine: String, thirdLine: String, town: String, zipCode: String)
case class ClubMember(person: Person, address: Address, paid: Boolean)

val codecRegistry = fromRegistries( fromProviders(classOf[ClubMember], classOf[Person], classOf[Address]), DEFAULT_CODEC_REGISTRY )

Sealed classes and ADTs

Hierarchical class structures are supported via sealed classes. Each subclass is handled specifically by the generated codec, so you only need create a CodecProvider for the parent sealed class. Internally an extra field (_t) is stored alongside the data so that the correct subclass can be hyrdated when decoding the data. Below is an example of a tree like structure containing branch and leaf nodes:

sealed class Tree
case class Branch(b1: Tree, b2: Tree, value: Int) extends Tree
case class Leaf(value: Int) extends Tree

val codecRegistry = fromRegistries( fromProviders(classOf[Tree]), DEFAULT_CODEC_REGISTRY )