reactivemongo-demo-app
reactivemongo-demo-app copied to clipboard
Problem with nested custom objects
Good afternoon Stéphane,
I am following your examples and they are great. However, I cannot make nested object works with 0.8.
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:*) } } }
Scala doesn't like the line "description" -> BSONArray(project.description)
However, the following alternative, which is similar to your examples, 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.getAsBSONObjectID, document.getAsBSONString.map(_.value).get, LocaleTextBSONReader.fromBSON(document.getAsBSONDocument.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) } }
I appreciate if you can shed some light on my problem. Thank you very much for your help.