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 the Play JSON compatibility for ReactiveMongo by adding the following dependency in your project/Build.scala
(or build.sbt
).
libraryDependencies ++= Seq(
"org.reactivemongo" %% "reactivemongo-play-json-compat" % "1.1.0-play29.RC14" // For Play 2.9.x (ajust accordingly)
)
This library is compatible from Play 2.5 to 3.0.
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.
The following import enables the compatibility.
import reactivemongo.play.json.compat._
Then JSON values can be converted to BSON, and it’s possible to convert BSON values to JSON.
import play.api.libs.json.JsValue
import reactivemongo.api.bson.BSONValue
import reactivemongo.play.json.compat._
def foo(v: BSONValue): JsValue = v // ValueConverters.fromValue
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.
The default 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 } |
Handlers
Conversions are provided between JSON and BSON handlers.
Considering the following User
class:
package object jsonsamples1 {
import reactivemongo.api.bson._
case class User(
_id: BSONObjectID, // Rather use UUID or String
username: String,
role: String,
created: BSONTimestamp, // Rather use Instance
lastModified: BSONDateTime,
sym: Option[BSONSymbol]) // Rather use String
object User {
implicit val bsonWriter: BSONDocumentWriter[User] = Macros.writer[User]
implicit val bsonReader: BSONDocumentReader[User] = Macros.reader[User]
}
}
The main import to use the handler conversions is:
import reactivemongo.play.json.compat._
Then specific imports are available to enable conversions, according the use cases.
import reactivemongo.play.json.compat._
// Conversions from BSON to JSON extended syntax
import bson2json._
// Override conversions with lax syntax
import lax._
// Conversions from JSON to BSON
import json2bson._
Convert BSON to JSON extended syntax:
Scala:
import _root_.play.api.libs.json._
import _root_.reactivemongo.api.bson._
// Global compatibility import:
import reactivemongo.play.json.compat._
// Import BSON to JSON extended syntax (default)
import bson2json._ // Required import
import jsonsamples1.User
val user1 = User(
BSONObjectID.generate(), "lorem", "ipsum",
created = BSONTimestamp(987654321L),
lastModified = BSONDateTime(123456789L),
sym = Some(BSONSymbol("foo")))
val userJs = Json.toJson(user1)
// Resolved from User.bsonReader
val jsonReader = implicitly[Reads[User]]
userJs.validate[User](jsonReader)
// => JsSuccess(user1)
// Resolved from User.bsonWriter
val jsonWriter: OWrites[User] = implicitly[OWrites[User]]
jsonWriter.writes(user1) // => userJs
JSON output: (userJs
)
{
"_id": {"$$oid":"..."},
"username": "lorem",
"role": "ipsum",
"created": {
"$$timestamp": {"t":0,"i":987654321}
},
"lastModified": {
"$$date": {"$$numberLong":"123456789"}
},
"sym": {
"$$symbol":"foo"
}
}
Convert BSON to JSON lax syntax:
Scala:
import _root_.play.api.libs.json._
import _root_.reactivemongo.api.bson._
// Global compatibility import:
import reactivemongo.play.json.compat._
// Import BSON to JSON extended syntax (default)
import bson2json._ // Required import
// Import lax overrides
import lax._
import jsonsamples1.User
val user2 = User(
BSONObjectID.generate(), "lorem", "ipsum",
created = BSONTimestamp(987654321L),
lastModified = BSONDateTime(123456789L),
sym = Some(BSONSymbol("foo")))
// Overrides BSONWriters for OID/Timestamp/DateTime
// so that the BSON representation matches the JSON lax one
implicit val bsonWriter: BSONDocumentWriter[User] = Macros.writer[User]
// Resolved from bsonWriter
val laxJsonWriter: OWrites[User] = implicitly[OWrites[User]]
val laxUserJs = laxJsonWriter.writes(user2)
// Overrides BSONReaders for OID/Timestamp/DateTime
// so that the BSON representation matches the JSON lax one
implicit val laxBsonReader: BSONDocumentReader[User] =
Macros.reader[User]
val laxJsonReader = implicitly[Reads[User]] // resolved from laxBsonReader
laxUserJs.validate[User](laxJsonReader)
// => JsSuccess(user2)
JSON output: (userLaxJs
)
{
"_id": "...",
"username": "lorem",
"role": "ipsum",
"created": 987654321,
"lastModified": 123456789,
"sym": "foo"
}
Convert JSON to BSON:
Considering the Street
class:
package object jsonsamples2 {
case class Street(
number: Option[Int],
name: String)
}
The BSON representation can be derived from the JSON as below.
import _root_.play.api.libs.json._
import _root_.reactivemongo.api.bson._
// Global compatibility import:
import reactivemongo.play.json.compat._
// Import JSON to BSON conversions
import json2bson._ // Required import
import jsonsamples2.Street
implicit val jsonFormat: OFormat[Street] = Json.format[Street]
// Expected BSON:
val doc = BSONDocument(
"number" -> 1,
"name" -> "rue de la lune")
val street = Street(Some(1), "rue de la lune")
// Resolved from jsonFormat
val bsonStreetWriter = implicitly[BSONDocumentWriter[Street]]
bsonStreetWriter.writeTry(street)
/* Success: doc = {
'number': 1,
'name': 'rue de la lune'
} */
// Resolved from jsonFormat
val bsonStreetReader = implicitly[BSONDocumentReader[Street]]
bsonStreetReader.readTry(doc)
// Success: street
Value converters:
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).
Troubleshooting
Missing json2bson
: If any of the following errors, then add the imports as below.
import reactivemongo.play.json.compat.json2bson._
Errors:
Implicit not found for '..': reactivemongo.api.bson.BSONReader[play.api.libs.json.JsObject]
Implicit not found for '..': reactivemongo.api.bson.BSONReader[play.api.libs.json.JsValue]
Implicit not found for '..': reactivemongo.api.bson.BSONWriter[play.api.libs.json.JsValue]
could not find implicit value for parameter writer: reactivemongo.api.bson.BSONDocumentWriter[AnyTypeProvideWithOWrites]
Missing JsObject
writer:
could not find implicit value for parameter e: reactivemongo.api.bson.BSONDocumentWriter[play.api.libs.json.JsObject]
import reactivemongo.play.json.compat.jsObjectWrites
Lax:
import reactivemongo.play.json.compat._,
json2bson._, lax._
Errors:
JsError(List((,List(JsonValidationError(List(Fails to handle _id: BSONString != BSONObjectID),WrappedArray())))))