Every BSON type has its matching class or object in the BSON library of ReactiveMongo.
BSONString for strings
BSONDouble for double values
BSONInteger for integer
BSONLong for long values
BSONObjectID for MongoDB object IDs
BSONDocument for MongoDB documents
BSONArray for MongoDB Arrays
BSONBinary for binary values (raw binary arrays stored in the document)
All this classes or objects extend the trait BSONValue.
You can build documents with the BSONDocument class. It accepts tuples of String and BSONValue.
Let’s build a very simple document representing an album.
You can read a BSONDocument using the get method, which will return an Option[BSONValue] whether the requested field is present or not.
Write values with BSONWriter
Writing a complex BSONDocument can be slightly verbose if you have to use BSONValue instances directly. Luckily ReactiveMongo provides BSONHandler, which is both BSONReader and BSONWriter. A BSONWriter[T, B <: BSONValue] is an instance that transforms some T instance into a BSONValue. A BSONReader[B <: BSONValue, T] is an instance that transforms a BSONValue into a T instance.
There are some predefined (implicit) handlers that are available when you import reactivemongo.bson._, including:
String <-> BSONString
Int <-> BSONInteger
Long <-> BSONLong
Double <-> BSONDouble
Boolean <-> BSONBoolean
Each value that can be written using a BSONWriter can be used directly when calling a BSONDocument constructor.
Easier, right? Note that this does not use implicit conversions, but rather implicit type classes.
Read values with BSONReader
Getting values follow the same principle using getAs(String) method. This method is parametrized with a type that can be transformed into a BSONValue using a BSONReader instance that is implicitly available in the scope (again, the default readers are already imported if you imported reactivemongo.bson._.) If the value could not be found, or if the reader could not deserialize it (often because the type did not match), None will be returned.
Another cool feature of BSONDocument constructors is to give Option[BSONValue] (or Options of instances that can be written into BSONValues). The resulting BSONDocument will contain only defined options.
Read/Write complex documents with BSONDocumentReader and BSONDocumentWriter
You can write your own Writers and Readers for your models. Let’s define a model for Album, and its BSONWriter and BSONReader.
You should have noted that our reader and writer extend BSONDocumentReader[T] and BSONDocumentWriter[T]. These two traits are just a shorthand for BSONReader[B <: BSONValue, T] and BSONWriter[T, B <: BSONValue].
OK, now, what if I want to store all the tracks names of the album? Or, in other words, how can we deal with collections? First of all, you can safely infer that all sequences and sets can be serialized as BSONArrays. Using BSONArray follows the same patterns as BSONDocument.
Using BSONArray does what we want, but this code is pretty verbose. Would it not be nice to deal directly with collections?
Here again, there is a converter for Traversables of types that can be transformed into BSONValues. For example, if you have a List[Something], if there is an implicit BSONWriter of Something to some BSONValue in the scope, you can use it as is, without giving explicitly a BSONArray. The same logic applies for reading BSONArray values.
So, now we can rewrite our reader and writer for albums including tracks.
Obviously, you can combine these readers and writers to de/serialize more complex object graphs. Let’s write an Artist model, containing a list of Albums.
Here, we get an “ambiguous implicit” problem, which is normal because we have more than one Reader of BSONDocuments available in our scope (SimpleArtistReader, ArtistReader, AlbumReader, etc.). So we have to explicitly give the type of the instance we want to get from the document.