- BSON
- Macros
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 hydrated 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 )
Options and None values.
By default Option
values are always stored. In 2.1.0 a new macro helpers were added so that None
values would not be stored in the
database. In the following example only if an address is present will it be stored in the database:
import org.mongodb.scala.bson.codecs.Macros
case class Person(firstName: String, secondName: String, address: Option[Address])
val personCodecProvider = Macros.createCodecProviderIgnoreNone[Person]()
Alternative field names
The BsonProperty
annotation can be used to configure a the bson
field key to be used for a given property. In the following example uses the BsonProperty
annotation to change how the firstName
property is stored:
case class Person(@BsonProperty("first_name") firstName: String, secondName: String)