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

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.

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

Scala Play Framework Json Serializer error

Trying to convert class to Json.
Here is my class, it includes other two classes:
case class GoodEdit(good: Good, data: List[(String, Option[GoodText])])
case class Good(
id: Long,
partnumber: Option[String] = None
)
case class GoodText(
goodid: Long,
languageid: Long,
title: String,
description: Option[String] = None)
And here are my writers:
object GoodWriters {
implicit val goodWrites = new Writes[Good] {
def writes(good: Good) = Json.obj(
"id" -> good.id,
"partnumber" -> good.partnumber
)
}
implicit val goodTextWrites = new Writes[GoodText] {
def writes(goodText: GoodText) = Json.obj(
"goodid" -> goodText.goodid,
"languageid" -> goodText.languageid,
"title" -> goodText.title,
"description" -> goodText.description
)
}
implicit val GoodEditWrites = new Writes[GoodEdit] {
def writes(goodEdit: GoodEdit) = Json.obj(
"good" -> Json.toJson(goodEdit.good),
"data" -> Json.toJson(
for ((lang, goodTextOpt) <- goodEdit.data ) yield Json.obj(lang -> goodTextOpt)
)
)
}
Then, in controller I try it to use like this:
Action {
import jwriters.GoodWriters._
GoodEditAggregate.get(id).map{
a => Ok(Json.toJson(a))
}.getOrElse(Ok(Json.toJson(Json.obj("status" -> "error","message" -> "Can't find good with this id"))))
}
And compilator complaining on this part: Ok(Json.toJson(a))
No Json serializer found for type GoodEdit. Try to implement an
implicit Writes or Format for this type
Can't understand what is wrong. I've imported writers for objects already
Try to Import Goodwrites globally

ReactiveMongo: How to convert a BSONArray to List[String]

Given the following BSONDocument...
val values = BSONDocument("values" -> BSONArray("one", "two", "three"))
How do I convert it to a List? I've tried this...
values.getAs[List[String]]("values").getOrElse(List.empty))
... but it doesn't work - I always get List.empty.
Am I missing something?
EDIT
OK... I think it is worth describing the real case. I ran the distinct command and this was the result:
values: {
values: [
0: BSONObjectID("55d0f641a100000401b7e454")
],
stats: {
n: BSONInteger(1),
nscanned: BSONInteger(1),
nscannedObjects: BSONInteger(1),
timems: BSONInteger(0),
cursor: BSONString(BtreeCursor projectId_1)
},
ok: BSONDouble(1.0)
}
I need to transform values to a Scala List[String] like this:
List("55d0f641a100000401b7e454")
Here is my solution. First, I've defined a BSONReader[BSONValue, String] like this...
package object bsonFormatters {
implicit object BSONValueStringReader extends BSONReader[BSONValue, String] {
def read(bson: BSONValue) = bson match {
case oid: BSONObjectID => oid.stringify
}
}
... and then just imported it in my companion object like this:
import reactivemongo.bson.{BSONString, BSONDocument}
import reactivemongo.core.commands.{CommandError, BSONCommandResultMaker, Command}
case class Distinct(
collectionName: String,
field: String,
query: Option[BSONDocument] = None
) extends Command[Seq[String]] {
override def makeDocuments = BSONDocument(
"distinct" -> BSONString(collectionName),
"key" -> field,
"query" -> query
)
val ResultMaker = Distinct
}
object Distinct extends BSONCommandResultMaker[Seq[String]] {
import bsonFormatters._ // this makes the trick
def apply(document: BSONDocument) = CommandError.checkOk(
document,
Some("distinct")
).toLeft(
document.getAs[List[String]]("values").getOrElse(List.empty)
)
}
I hope it helps.

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]

Nested document with reactive mongo and Scala

I'm trying to store a nested document in MongoDB through Scala. The document looks like:
Project {
"_id": ObjectId("528547370cf6e41449003512"),
"highLevelCode": NumberLong(3),
"description": [
{"_id": ObjectId("528547370cf6e41449003521"),
"lang": "en",
"desc": "desc in English"},
{"_id ": ObjectId("528547370cf6e41449003522"),
"lang": "fr",
"desc": "desc en francais"}],
"budget": NumberLong(12345)
}
Basically I want to store nested descriptions, which could be of multiple languages in the Project document.
The code I wrote is:
import reactivemongo.bson._
import reactivemongo.bson.handlers.{BSONWriter, BSONReader}
import reactivemongo.bson.BSONLong
import reactivemongo.bson.BSONString
case class LocaleText(
id: Option[BSONObjectID],
lang: String,
textDesc: String
)
object LocaleText {
implicit object LocaleTextBSONReader extends BSONReader[LocaleText] {
def fromBSON(document: BSONDocument): LocaleText = {
val doc = document.toTraversable
LocaleText(
doc.getAs[BSONObjectID]("_id"),
doc.getAs[BSONString]("lang").map(_.value).get,
doc.getAs[BSONString]("textDesc").map(_.value).get
)
}
}
implicit object LocaleTextBSONWriter extends BSONWriter[LocaleText] {
def toBSON(localText: LocaleText) = {
BSONDocument(
"_id" -> localText.id.getOrElse(BSONObjectID.generate),
"lang" -> BSONString(localText.lang),
"textDesc" -> BSONString(localText.textDesc)
)
}
}
}
case class Project(
id: Option[BSONObjectID],
description: List[LocaleText],
budget: Option[Long]
)
object Project {
implicit object ProjectReader extends BSONReader[Project]{
def fromBSON(doc: BSONDocument): Project = {
val document = doc.toTraversable
Project(
document.getAs[BSONObjectID]("_id"),
document.getAs[BSONArray]("description").map { values =>
values.values.toList.flatMap { case value =>
value match {
case v: LocaleText => Some(v.asInstanceOf[LocaleText])
case _ => None
}
}
}.getOrElse(List.empty),
document.getAs[BSONLong]("budget").map(_.value)
)
}
}
implicit object ProjectWriter extends BSONWriter[Project]{
def toBSON(project: Project): BSONDocument = {
BSONDocument(
"_id" -> project.id.getOrElse(BSONObjectID.generate),
"description" -> BSONArray(project.description)
).append(Seq(
project.budget.map(b => "budget" -> BSONLong(b))
).flatten:_*)
}
}
}
However, it gave me compilation error like
overloaded method value apply with alternatives: [error] (producer: reactivemongo.bson.Implicits.Producer[(String, reactivemongo.bson.BSONValue)],producers: reactivemongo.bson.Implicits.Producer[(String, reactivemongo.bson.BSONValue)])reactivemongo.bson.AppendableBSONDocument
[error] (els: (String, reactivemongo.bson.BSONValue))reactivemongo.bson.AppendableBSONDocument
[error] cannot be applied to ((String, reactivemongo.bson.BSONObjectID), List[LocaleText])...
Basically Scala doesn't like the line
"description" -> BSONArray(project.description)
However, the following alternative works although I cannot use a List/Array to allow more than two languages:
case class LocaleText(
enDesc: String,
frDesc: String)
case class Project(
id: Option[BSONObjectID],
description: LocaleText)
object Project {
implicit object LocaleTextBSONReader extends BSONReader[LocaleText] {
def fromBSON(document: BSONDocument): LocaleText = {
val doc = document.toTraversable
LocaleText(
doc.getAs[BSONString]("enDesc").map(_.value).get,
doc.getAs[BSONString]("frDesc").map(_.value).get
)
}
}
implicit object LocaleTextBSONWriter extends BSONWriter[LocaleText] {
def toBSON(localText: LocaleText) = {
BSONDocument(
"enDesc" -> BSONString(localText.enDesc),
"frDesc" -> BSONString(localText.frDesc)
)
}
}
implicit object ProjectReader extends BSONReader[Project]{
def fromBSON(doc: BSONDocument): Project = {
val document = doc.toTraversable
Project(
document.getAs[BSONObjectID]("_id"),
document.getAs[BSONString]("iatiId").map(_.value).get,
LocaleTextBSONReader.fromBSON(document.getAs[BSONDocument]("description").get)
}
}
implicit object ProjectWriter extends BSONWriter[Project]{
def toBSON(project: Project): BSONDocument = {
BSONDocument(
"_id" -> project.id.getOrElse(BSONObjectID.generate),
"iatiId" -> BSONString(project.iatiId),
"description" -> LocaleTextBSONWriter.toBSON(project.description)
}
}
How can I convert project.description, which a List of LocaleText to BSONArray for Mongo? I appreciate if you can shed some light on my problem. Thank you very much for your help.
Finally I found the solution to my own question, hope this will help some others who struggle with ReactiveMongo 0.8 as well:
case class LocaleText(
lang: String,
desc: String)
case class Project(
id: Option[BSONObjectID],
descriptions: List[LocaleText])
object Project {
implicit object LocaleTextBSONReader extends BSONReader[LocaleText] {
def fromBSON(document: BSONDocument): LocaleText = {
val doc = document.toTraversable
LocaleText(
doc.getAs[BSONString]("lang").get.value,
doc.getAs[BSONString]("desc").get.value
)
}
}
implicit object LocaleTextBSONWriter extends BSONWriter[LocaleText] {
def toBSON(localText: LocaleText) = {
BSONDocument(
"lang" -> BSONString(localText.lang),
"desc" -> BSONString(localText.desc)
)
}
}
implicit object ProjectReader extends BSONReader[Project]{
def fromBSON(doc: BSONDocument): Project = {
val document = doc.toTraversable
Project(
document.getAs[BSONObjectID]("_id"),
document.getAs[BSONArray]("descriptions").get.toTraversable.toList.map { descText =>
LocaleTextBSONReader.fromBSON(descText.asInstanceOf[TraversableBSONDocument]
}
)
}
}
implicit object ProjectWriter extends BSONWriter[Project]{
def toBSON(project: Project): BSONDocument = {
BSONDocument(
"_id" -> project.id.getOrElse(BSONObjectID.generate),
"descriptions" -> BSONArray(project.descriptions.map {
description => LocaleTextBSONWriter.toBSON(description)
}: _*)
}
}
It might be an issue in the library. I tested your code using the latest version of reactivemongo and it compiled just fine (I needed to adapt your code to fit the new syntax for BSONReaders and BSONWriters but that shouldn't have any influence on the error).
Using reactivemongo 0.10.0 you can even use the newly provided macros:
import reactivemongo.bson._
case class LocaleText(id: Option[BSONObjectID], lang: String, textDesc: String)
object LocaleText {
implicit val localeTextBSONHandler = Macros.handler[LocaleText]
}
case class Project(id: Option[BSONObjectID], description: List[LocaleText], budget: Option[Long])
object Project {
implicit val projectBSONHandler = Macros.handler[Project]
}