Reactive Scala Driver for MongoDB
Asynchronous & Non-Blocking
The ReactiveMongo Play JSON library provides a JSON serialization pack for ReactiveMongo, based on the Play Framework JSON library.
Setup
You can setup this serialization pack by adding the following dependency in your project/Build.scala
(or build.sbt
).
libraryDependencies ++= Seq(
"org.reactivemongo" %% "reactivemongo-play-json" % "0.20.13-play27"
)
If the dependency for the Play plugin (with the right version) is present, it already provides the JSON support and this JSON serialization pack must not be added as a separate dependency.
Then, the following code enables this JSON serialization pack.
import reactivemongo.play.json._
API documentations: ReactiveMongo Play JSON API
If you want to use this JSON serialization outside of Play application, the dependency to the standalone Play JSON library must then be added:
"com.typesafe.play" %% "play-json" % version
.
Documents and values
There is one Play JSON class for most of the BSON types, from the play.api.libs.json
package:
All these JSON types extend JsValue
, thus any JSON value can be converted to an appropriate BSON value.
This serialization is based on the MongoDB Extension JSON syntax (e.g. { "$oid": "<id>" }
for a Object ID):
BSON | JSON |
---|---|
BSONDocument | JsObject |
BSONArray | JsArray |
BSONBinary | JsObject with a $binary JsString field containing the value in hexadecimal representation |
BSONBoolean | JsBoolean |
BSONDBPointer | No JSON type |
BSONDateTime | JsObject with a $date JsNumber field with the timestamp (milliseconds) as value |
BSONDouble | JsNumber or JsObject with $numberDouble value |
BSONInteger | JsNumber or JsObject with $numberInt value |
BSONJavaScript | JsObject with a $javascript JsString value representing the JavaScript code |
BSONLong | JsNumber or JsObject with $numberLong value |
BSONMaxKey | JsObject as constant { "$maxKey": 1 } |
BSONMinKey | JsObject as constant { "$minKey": 1 } |
BSONNull | No JSON type |
BSONObjectID | JsObject with a $oid JsString field with the stringified ID as value |
BSONRegex | JsObject with a $regex JsString field with the regular expression, and optionally an $options JsString field with the regex flags (e.g. "i" for case insensitive) |
BSONString | JsString |
BSONSymbol | JsObject with a $symbol JsString field with the symbol name as value |
BSONTimestamp | JsObject with a $timestamp nested object having a t and a i JsNumber fields |
BSONUndefined | JsObject of the form { "$undefined": true } |
Furthermore, the whole library is articulated around the concept of Writes
and Reads
. These are typeclasses whose purpose is to serialize/deserialize objects of arbitrary types into/from JSON.
Using that, any type that can be serialized as JSON can be also be serialized as BSON.
A document is represented by JsObject
, which is basically an immutable list of key-value pairs. Since it is the most used JSON type when working with MongoDB, the ReactiveMongo Play JSON library handles such JsObject
s as seamless as possible. The encoding of such JSON object needs an instance of the typeclass OWrites
(a Writes
specialized for object).
The default JSON serialization can also be customized, using the functions BSONFormats.readAsBSONValue
and BSONFormats.writeAsJsValue
.
JSON collections
This library provides a specialized collection reference called JSONCollection
that deals naturally with JsValue
and JsObject
. Thanks to it, you can fetch documents from MongoDB in the Play JSON format, transform them by removing and/or adding some properties, and send them to the client.
import scala.concurrent.{ ExecutionContext, Future }
import play.api.libs.json._
import reactivemongo.api.{ Cursor, ReadPreference }
import reactivemongo.play.json._, collection._
def jsonFind(coll: JSONCollection)(implicit ec: ExecutionContext): Future[List[JsObject]] =
coll.find(Json.obj()).sort(Json.obj("updated" -> -1)).
cursor[JsObject](ReadPreference.Primary).
collect[List](-1, Cursor.FailOnError[List[JsObject]]())
Even better, when a client sends a JSON document, you can validate it and transform it before saving it into a MongoDB collection (coast-to-coast approach).
JSON cursors
The support of Play JSON for ReactiveMongo provides some extensions of the result cursors, as .jsArray()
to read underlying data as a JSON array.
import scala.concurrent.Future
import play.api.libs.json._
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import reactivemongo.api.ReadPreference
import reactivemongo.play.json._
import reactivemongo.play.json.collection.{
JSONCollection, JsCursor
}, JsCursor._
def jsAll(collection: JSONCollection): Future[JsArray] = {
type ResultType = JsObject // any type which is provided a `Writes[T]`
collection.find(Json.obj()).
cursor[ResultType](ReadPreference.Primary).jsArray()
}
In the previous example, the function jsAll
will return a JSON array containing all the documents of the given collection (as JSON objects).
Helpers
There are some helpers coming along with the JSON support.
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import reactivemongo.play.json.collection._
// Import a list of JSON objects as documents into the JSON `collection`,
// and returns the insertion count.
def importJson(collection: JSONCollection, resource: String): Future[Int] =
Helpers.bulkInsert(collection, getClass.getResourceAsStream(resource)).
map(_.totalN)
As illustrated by the previous example, the function Helpers.bulkInsert
provides a JSON import feature.
Run a raw command
The command API can be used with the JSON serialization to execution a JSON object as a raw command.
import scala.concurrent.{ ExecutionContext, Future }
import play.api.libs.json.{ JsObject, Json }
import reactivemongo.play.json._
import reactivemongo.api.{ ReadPreference, FailoverStrategy }
import reactivemongo.api.commands.Command
def rawResult(db: reactivemongo.api.DefaultDB)(implicit ec: ExecutionContext): Future[JsObject] = {
val commandDoc = Json.obj(
"aggregate" -> "orders", // we aggregate on collection `orders`
"pipeline" -> List(
Json.obj("$match" -> Json.obj("status" -> "A")),
Json.obj(
"$group" -> Json.obj(
"_id" -> "$cust_id",
"total" -> Json.obj("$sum" -> "$amount"))),
Json.obj("$sort" -> Json.obj("total" -> -1))
)
)
val runner = Command.run(JSONSerializationPack, FailoverStrategy())
runner.apply(db, runner.rawCommand(commandDoc)).
one[JsObject](ReadPreference.Primary)
}
Troubleshooting
If the following error is raised;
No Json serializer as JsObject found for type play.api.libs.json.JsObject.
Try to implement an implicit OWrites or OFormat for this type.
It’s necessary to make sure the right imports are there.
import reactivemongo.play.json._
// import the default BSON/JSON conversions
If the following error is raised;
Error:(X, Y) type mismatch;
found : reactivemongo.api.bson.collection.BSONCollection
required: ... reactivemongo.play.json.collection.JSONCollection ...
It’s necessary to also make sure the right imports are there.
import reactivemongo.play.json.collection._