Insert document with ReactiveMongo does not use BSONWriter or Reader - mongodb

I have the following class:
case class DeviceRegistration(deviceData: DeviceData,
pin: String,
created: DateTime) {}
Type DeviceData is defined simply as 4 string fields.
I've been trying unsuccessfully to insert a type of DeviceRegistration to a mongo collection. I want to make sure the date is stored as a ISODate and not a NumberLong, so I implemented custom readers and writers.
implicit object DeviceDataWriter extends BSONDocumentWriter[DeviceData] {
def write(data: DeviceData): BSONDocument = BSONDocument(
DEVICE_ID_KEY -> data.deviceId,
MODEL_KEY -> data.model,
BRAND_KEY -> data.brand,
MANUFACTURER_KEY -> data.manufacturer)
}
implicit object DeviceRegistrationWriter extends BSONDocumentWriter[DeviceRegistration] {
def write(registration: DeviceRegistration): BSONDocument = BSONDocument(
DEVICE_DATA_KEY -> registration.deviceData,
PIN_KEY -> registration.pin,
CREATED_KEY -> BSONDateTime(registration.created.getMillis))
}
implicit object DeviceDataReader extends BSONDocumentReader[DeviceData] {
def read(doc: BSONDocument): DeviceData = {
val deviceId = doc.getAs[String](DEVICE_ID_KEY).get
val model = doc.getAs[String](MODEL_KEY)
val brand = doc.getAs[String](BRAND_KEY)
val manufacturer = doc.getAs[String](MANUFACTURER_KEY)
DeviceData(deviceId, model, brand, manufacturer)
}
}
implicit object DeviceRegistrationReader extends BSONDocumentReader[DeviceRegistration] {
def read(doc: BSONDocument): DeviceRegistration = {
val deviceData = doc.getAs[DeviceData](DEVICE_DATA_KEY).get
val pin = doc.getAs[String](PIN_KEY).get
val created = doc.getAs[BSONDateTime](CREATED_KEY).map(dt => new DateTime(dt.value))
DeviceRegistration(deviceData, pin, created.get)
}
}
I'm trying to insert the document with the following code:
def save(deviceRegistration: DeviceRegistration): Future[DeviceRegistration] = {
deviceRegistrations.insert(deviceRegistration).map(result => deviceRegistration)
}
To retrieve, I'm doing this:
def findDeviceRegistrationRequest(deviceConfirmationData: DeviceConfirmationData) = {
deviceRegistrations.find(BSONDocument("pin" -> deviceConfirmationData.pin))
.one[DeviceRegistration](ReadPreference.Primary)
}
The record is stored as this:
{ "_id" : ObjectId("56dea8d0d8cadd6ff70690d8"), "deviceData" : { "deviceId" : "kdsajkldjsalkda" }, "pin" : "9914", "created" : NumberLong("1457432783921") }
The created date is clearly not being serialized by my writer. It seems reactivemongo is using some default writer.
Likewise, when I read, I get an exception:
Caused by: java.lang.RuntimeException: (/created,List(ValidationError(List(error.expected.date),WrappedArray())))
So it's clearly also not using my reader.
I didn't have any luck googling around. What am I missing?
I also tried to find a way to specifically set the writer and reader I want to use (instead of relying on the implicit mechanism), but I was not able to figure it out.
Any pointers in the right direction would be most appreciated.

Related

No Json serializer as JsObject found for type model.User. Try to implement an implicit OWrites or OFormat for this type

I'm using Play framework with Scala and Reactive Mongo to save an object into my mongodb database. Following this http://reactivemongo.org/releases/0.10/documentation/bson/usage.html I came up with the following code:
import java.util.Date
import com.google.inject.Inject
import model.User
import play.modules.reactivemongo.ReactiveMongoApi
import play.modules.reactivemongo.json.collection.JSONCollection
import reactivemongo.bson.{BSONDocument, BSONDocumentReader, BSONDocumentWriter, BSONObjectID}
import play.modules.reactivemongo.json._, ImplicitBSONHandlers._
import json.JsonFormatters._
class UserRepository #Inject() (val reactiveMongoApi : ReactiveMongoApi) {
private def users = reactiveMongoApi.db.collection[JSONCollection]("users")
def save(user: User) = {
users.insert(user)
}
implicit object UserWriter extends BSONDocumentWriter[User] {
def write(user: User) = {
BSONDocument(
"_id" -> Option(user.id).getOrElse(BSONObjectID.generate),
"name" -> user.name,
"email" -> user.email,
"companyName" -> user.companyName,
"created" -> Option(user.created).getOrElse(new Date)
)
}
}
implicit object UserReader extends BSONDocumentReader[User] {
def read(doc: BSONDocument): User = {
User(
doc.getAs[BSONObjectID]("_id").get,
doc.getAs[String]("name").get,
doc.getAs[String]("email").get,
doc.getAs[String]("companyName").get,
doc.getAs[Date]("created").get
)
}
}
}
I created my implicit writers to convert to a BsonDocument, so I was expecting it to be properly converted and saved into the database.
However, when I compile, I get:
UserRepository.scala:18: No Json serializer as JsObject found for type model.User. Try to implement an implicit OWrites or OFormat for this type.
[error] Error occurred in an application involving default arguments.
[error] users.insert(user)
I'm importing necessary packages as mentioned in No Json serializer as JsObject found for type play.api.libs.json.JsObject.
I'm also importing json.JsonFormatters._ which includes:
implicit val userWrites : Format[User] = Json.format[User]
Yet, it's still returning the same error telling me it can't convert from JsObject to User. I fail to see where is the JsObject here, considering my User entity is just a case class with 5 fields.
case class User(var id: BSONObjectID, var name: String, var email: String, var companyName: String, var created: Date) {
}
Any ideas? What am I missing?
You are using BSONObjectID, and you do not have implicit OFormat for that type. Try this:
implicit val format = Json.format[User]
implicit val userFormats = new OFormat[User] {
override def reads(json: JsValue): JsResult[User] = format.reads(json)
override def writes(o: User): JsObject = format.writes(o).asInstanceOf[JsObject]
}
Typically, BSONObjectID (_id) does not make any sense in the application. Therefore, you should not use it. If you need an Id, for example as a running number, for users, you can define a new field. This is an example: https://github.com/luongbalinh/play-mongo/blob/master/app/models/User.scala

I cannot retrieve BSONDocument correctly

I have been spending time 3 hours and I wasn't success trying to retrieve a BSONDocument with all of the attributes. I don't why I only get the first attribute... where are the others?
I wrote the following test
describe("Testing reactive map user") {
it(" convert an user to BSONDocument") {
val basicProfile = BasicProfile("1", "1", Option("pesk"), Option("pesk"),
Option("pesk pesk"), Option("pesk#gmail.com"), Option("url"),
AuthenticationMethod.UserPassword, Option(new OAuth1Info("token", "secret")),
Option(new OAuth2Info("token", Option("secret"))),
Option(new PasswordInfo("hasher", "password", Option("salt"))))
val user = User(Option(BSONObjectID.generate), basicProfile, List(), "31.55, 53.66", List[User](), Option(DateTime.now))
val result = User.UserBSONWriter.write(user)
assert(result.getAs[String]("providerId") == "1")
}
}
The UserBSONWriter
implicit object UserBSONWriter extends BSONDocumentWriter[User] {
def write(user: User): BSONDocument = {
val doc = BSONDocument(
"_id" -> user.id.getOrElse(BSONObjectID.generate),
"providerId" -> BSONString(user.basicProfile.providerId),
"userId" -> BSONString(user.basicProfile.userId))
println(doc)
doc
}
}
And I attach the screenshot of the console. I'm trying to get the value providerId, which is next to BSONObjectID, but I can just only get the first attribute.
I will appreciate a lot if somebody can help me. And I have other comment, Im getting some headaches because of the implicitly system which is used by scala's BSON API. It is not trivial to find some docs about these all implicit conversions.
Outupt of println(doc)
BSONDocument(non-empty)

Isn't there any other way to convert bson objects to case class and the vice-versa rather than the implicit conversion in scala?

I have went through some of the reactive mongo example and find the conversion of the model and the bson object as below.
case class Artist(
name: String,
slug: String,
artistId: Int,
albums: Vector[Album] = Vector(),
id: ObjectId = new ObjectId())
object ArtistMap {
def toBson(artist: Artist): DBObject = {
MongoDBObject(
"name" -> artist.name,
"slug" -> artist.slug,
"artistId" -> artist.artistId,
"albums" -> artist.albums.map(AlbumMap.toBson),
"_id" -> artist.id
)
}
def fromBson(o: DBObject): Artist = {
Artist(
name = o.as[String]("name"),
slug = o.as[String]("slug"),
artistId = o.as[Int]("artistId"),
albums = o.as[MongoDBList]("albums").toVector
.map(doc => AlbumMap.fromBson(doc.asInstanceOf[DBObject])),
id = o.as[ObjectId]("_id")
)
}
}
Is there any other way to get rid of this overhead of mapping each field of the case classes, maybe some framework over reactivemongo or any utility for this?
I don't understand your comment too, but my assumption you want functionality like that.
(I haven't written on scala for a few month, so sorry for any stupid mistake.)
case class Album(name: String, year: Int)
/*
then you may implement serialization interface(trait) or use macros
*/
implicit val albumHandler = Macros.handler[Album]
/*
I'm not sure is there build in implementation for the Vector[].
*/
object VectorHandler[T](implicit handler: BSONHandler[BSONValue, T]) extends BSONHandler[BSONArray, Vector[T]]{
def read(bson: BSONArray): Vector[T]{
/*
iterate over array and serialize it in a Vector with a help of the handler,
or just persist Vector[] as List[]
*/
}
def write(t: Vector[T]): BSONArray {
}
}
implicit val albumVectorHandler = VectorHandler[Album]()
implicit val artistHandler = Macros.handler[Atrist]

How to Convert MongoDBObject to JsonString

My mongoDb collection looks like this:
> db.FakeCollection.find().pretty()
{
"_id" : ObjectId("52b2d71c5c197846fd3a2737"),
"categories" : [
{
"categoryname" : "entertainment",
"categoryId" : "d3ffca550ae44904aedf77cdcbd31d7a",
"displayname" : "Entertainment",
"subcategories" : [
{
"subcategoryname" : "games",
"subcategoryId" : "ff3d0cbeb0eb4960b11b47d7fc64991b",
"displayname" : "Games"
}
]
}
]
}
I want to write a test case for the below collection using Specs2 JsonMatchers in scala with MongodbCasbah.
How do I convert DBObjects to Strings?
I believe your approach is slightly wrong here. Your collection should look like:
class Category extends BsonRecord[Category] {
def meta = Category
object categoryname extends StringField(this, 200)
object categoryId extends StringField(this, 64)
object displayname extends StringField(this, 100)
object subcategories extends BsonRecordListField(this, Category)
}
object Category extends Category with BsonMetaRecord[Category] {
}
class FakeCollection extends MongoRecord[FakeCollection] with ObjectIdPk[FakeCollection] {
def meta = FakeCollection
object categories extends BsonRecordListField(this, Category)
}
object FakeCollection extends FakeCollection with MongoMetaRecord[FakeCollection] {
override def collectionName = "fakecollection"
def getEntryByName: List[Category] = {
FakeCollection.find
}
}
With that method you can do:
import net.liftweb.json.JsonAST.JValue;
import net.liftweb.http.js.JsExp;
import net.liftweb.http.js.JsExp._;
import net.liftweb.json.JsonDSL.seq2jvalue
val json: JsExp = seq2JValue(FakeColleciton.find.map(_.asJValue))
val stringContent = json.toJsCmd; // now it's here, you can match.
Have a look HERE, see how you can add Foursquare Rogue to make your life easier.
Short answer:
val doc: com.mongodb.DBObject = ???
pretty(render(net.liftweb.mongodb.JObjectParser.serialize(doc)))
Long answer that explains what's going on. I included full type names for clarity:
import net.liftweb.mongodb.JObjectParser
import net.liftweb.json.DefaultFormats
// default JSON formats for `parse` and `serialize` below
implicit val formats = DefaultFormats
// Convert DBObject to JValue:
val doc: com.mongodb.DBObject = ??? // get it somehow
val jsonDoc: net.liftweb.json.JValue = JObjectParser.serialize(doc)
// Convert JValue to DBObject:
val doc2: net.liftweb.json.JObject = ???
val dbObj: com.mongodb.DBObject = JObjectParser.parse(doc2)
// Render JSON as String:
import net.liftweb.json._
pretty(render(jsonDoc))
// or use compactRender, compact(render(jsonDoc)), etc
To compare JSON docs there is Diff: val Diff(changed, added, deleted) = json1 diff json2.
More info here: https://github.com/lift/lift/tree/master/framework/lift-base/lift-json/.
You can test with specs2 and Lift Diff this way for example:
json1 diff json2 mustEqual Diff(changedJson, addedJson, JNothing)

How do I map my objects correctly to make a list of objects work in mongo + play2

I'm trying to write readers/writers for my case classes after reading:
https://github.com/sgodbillon/reactivemongo-demo-app/blob/master/app/models/articles.scala
https://github.com/zenexity/ReactiveMongo/blob/master/driver/samples/BSON.scala
but I'm having trouble in making it work.
I have a leadCategory that can consist of several word objects.
package models
import org.joda.time.DateTime
import reactivemongo.bson._
import reactivemongo.bson.handlers.{BSONWriter, BSONReader}
import reactivemongo.bson.BSONDateTime
import reactivemongo.bson.BSONString
LeadCategory:
case class LeadCategory(
id: Option[BSONObjectID],
categoryId: Long,
categoryName: String,
creationDate: Option[DateTime],
updateDate: Option[DateTime],
words: List[Word]
)
object LeadCategory {
implicit object LeadCategoryBSONReader extends BSONReader[LeadCategory] {
def fromBSON(document: BSONDocument): LeadCategory = {
val doc = document.toTraversable
LeadCategory(
doc.getAs[BSONObjectID]("_id"),
doc.getAs[BSONLong]("caregoryId").get.value,
doc.getAs[BSONString]("categoryName").get.value,
doc.getAs[BSONDateTime]("creationDate").map(dt => new DateTime(dt.value)),
doc.getAs[BSONDateTime]("updateDate").map(dt => new DateTime(dt.value)),
doc.getAs[List[Word]]("words").toList.flatten)
}
}
implicit object LeadCategoryBSONWriter extends BSONWriter[LeadCategory] {
def toBSON(leadCategory: LeadCategory) = {
BSONDocument(
"_id" -> leadCategory.id.getOrElse(BSONObjectID.generate),
"caregoryId" -> BSONLong(leadCategory.categoryId),
"categoryName" -> BSONString(leadCategory.categoryName),
"creationDate" -> leadCategory.creationDate.map(date => BSONDateTime(date.getMillis)),
"updateDate" -> leadCategory.updateDate.map(date => BSONDateTime(date.getMillis)),
"words" -> leadCategory.words)
}
}
Word:
case class Word(id: Option[BSONObjectID], word: String)
object Word {
implicit object WordBSONReader extends BSONReader[Word] {
def fromBSON(document: BSONDocument): Word = {
val doc = document.toTraversable
Word(
doc.getAs[BSONObjectID]("_id"),
doc.getAs[BSONString]("word").get.value
)
}
}
implicit object WordBSONWriter extends BSONWriter[Word] {
def toBSON(word: Word) = {
BSONDocument(
"_id" -> word.id.getOrElse(BSONObjectID.generate),
"word" -> BSONString(word.word)
)
}
}
}
I'm getting a compile error at:
"words" -> leadCategory.words)
stating:
Too many arguments for method apply(ChannelBuffer)
Type mismatch found: (String, List[Words]) required required implicits.Producer[(String, BsonValue)]
What have I missed? Maybe I have misunderstood the docs...
Try to declare the "Word" implicit objects above the "LeadCategory", if you are putting all of it in the same file.
Scala cannot find the implicit objects for the List[Word] - assuming you are using ReactiveMongo 0.9.
All of your types should a derivative of a BSON type. I think you want to use BSONArray instead of a List.