Scala Play Framework Json Serializer error - scala

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

Related

How to include more than one item in my json object in scala?

I'm trying to include more than one item in my json object (currently I can only add one item),
In my scala class I have:
case class Api(row:Int,name:String,age:Int)
Ok(Json.obj("status" -> "ok", "cars" -> carsBooked.map(_.row), "saved" -> saved, "failed" -> failed, "notBooked" -> Response.errors)) <<---- this works perfectly and I'm able to get a list of all the row in json, but how do i add name and age?
Ideally I wanna do something like carsBooked.map(_.row._name etc..)
Thanks!
If we have
case class Api(row: Int, name: String, age: Int)
then we can generate codecs automatically.
object Api {
implicit val apiReads: Reads[Api] = Json.reads[Api]
implicit val apiWrites: OWrites[Api] = Writes.writes[Api]
}
and then we could write:
Ok(Json.obj("status" -> "ok", "cars" -> Json.toJson(carsBooked), "saved" -> saved, "failed" -> failed, "notBooked" -> Response.errors))
or we can actually make this also automated:
case class ApiResult(
status: String,
cars: List[Api],
saved: List[String],
failed: List[String],
notSaved: List[String]
)
object ApiResult {
implicit val apiResultReads: Reads[ApiResult] = Json.reads[ApiResult]
implicit val apiResultWrites: OWrites[ApiResult] = Writes.writes[ApiResult]
}
val apiResult: ApiResult = ...
Ok(Json.toJson(apiResult))

Mapping Play Framework form element to custom type

I would like to map form element to java.sql.Time the same way I am able to map java.sql.Date. I have the following piece of code:
case class Item (timeField: java.sql.Time, dateField: java.sql.Date)
val itemForm = Form(
mapping(
"timeField" -> sqlDate("H:mm"),
"dateField" -> sqlDate("yyyy\M\d")
)(Item.apply)(Item.unapply)
)
Mapping do dateField works well, but is it possible to create custom mapping e.g. for time field? I'm expecting something like this
val itemForm = Form(
mapping(
"timeField" -> sqlTime("H:mm"), //my own mapping function
"dateField" -> sqlDate("yyyy\M\d")
)(Item.apply)(Item.unapply)
)
You could achieve this using a custom Formatter. The code would look something like this:
def sqlTime(pattern: String) = of(sqlTimeFormat(pattern))
def sqlTimeFormat(pattern: String): Formatter[java.sql.Time] = new Formatter[java.sql.Time] {
def bind(key: String, data: Map[String, String]) = {
try {
val sdf = new SimpleDateFormat(pattern)
Right(new java.sql.Time(sdf.parse(data.getOrElse(key, "")).getTime))
} catch {
case e: Exception => Left(List(FormError(key, "Your error message")))
}
}
def unbind(key: String, value: java.sql.Time) = Map(key -> value.formatted(pattern))
}
Note: I haven't tested this, you may have to make a few tweaks, but it should be close.

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.

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]
}

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.