Apache Spark Object not Serializable Exception for json parser - json

I am reading the data[json as String] from kafka queue and tring to parse json as String into case class using liftweb json api.
here is the code Snippet
val sparkStreamingContext = new StreamingContext(sparkConf, Seconds(5))
val kafkaParam: Map[String, String] = Map(
"bootstrap.servers" -> kafkaServer,
"key.deserializer" -> classOf[StringDeserializer].getCanonicalName,
"value.deserializer" -> classOf[StringDeserializer].getCanonicalName,
"zookeeper.connect" -> zookeeperUrl,
"group.id" -> "demo-group")
import org.apache.spark.streaming.kafka._
import net.liftweb.json.{DefaultFormats, Formats}
import net.liftweb.json._
val topicSet = Map(kafkaTopic -> 1)
val streaming = KafkaUtils.createStream[String, String, StringDecoder, StringDecoder](sparkStreamingContext, kafkaParam, topicSet, StorageLevel.MEMORY_AND_DISK)
streaming.map { case (id, tweet) => implicit val formats: Formats = DefaultFormats
(id, parse(tweet).extract[Tweet])
}.print()
sparkStreamingContext.start()
sparkStreamingContext.awaitTermination()
and i am getting this exception
Exception in thread "main" org.apache.spark.SparkException: Job aborted due to stage failure: Task 0.0 in stage 1.0 (TID 1) had a not serializable result: net.liftweb.json.DefaultFormats$
Serialization stack:
- object not serializable (class: net.liftweb.json.DefaultFormats$, value: net.liftweb.json.DefaultFormats$#74a2fec)
- field (class: Tweet, name: formats, type: interface net.liftweb.json.Formats)
- object (class Tweet, Tweet(Akash24,Adele))
- field (class: scala.Tuple2, name: _2, type: class java.lang.Object)
- object (class scala.Tuple2, (1,Tweet(Akash24,Adele)))
- element of array (index: 0)
- array (class [Lscala.Tuple2;, size 11)
Can anyone help me fix this problem
Any Help will be appreciate
Thanks

From the logs it looks like a simple exception of Class not Serializable. to correct is use following code:
sparkConf.registerKryoClasses(Array(classOf[DefaultFormats]))
val sparkStreamingContext = new StreamingContext(sparkConf, Seconds(5))
val kafkaParam: Map[String, String] = Map(
"bootstrap.servers" -> kafkaServer,
"key.deserializer" -> classOf[StringDeserializer].getCanonicalName,
"value.deserializer" -> classOf[StringDeserializer].getCanonicalName,
"zookeeper.connect" -> zookeeperUrl,
"group.id" -> "demo-group")
import org.apache.spark.streaming.kafka._
import net.liftweb.json.{DefaultFormats, Formats}
import net.liftweb.json._
val topicSet = Map(kafkaTopic -> 1)
val streaming = KafkaUtils.createStream[String, String, StringDecoder, StringDecoder](sparkStreamingContext, kafkaParam, topicSet, StorageLevel.MEMORY_AND_DISK)
streaming.map { case (id, tweet) => implicit val formats: Formats = DefaultFormats
(id, parse(tweet).extract[Tweet])
}.print()
sparkStreamingContext.start()
sparkStreamingContext.awaitTermination()
It will make the DefaultFormats class serializable and Spark master will able to send implicit val formats to all worker nodes.

Related

How do I override default codec in circe?

I'd like to encode Array[Byte] fields of my case classes as Base64 strings. For some reason Circe doesn't pick up my codec using default one instead that converts byte array into json array of ints.
What should I do to fix it ? Here is my minimized code
import io.circe.generic.JsonCodec
sealed trait DocumentAttribute
#JsonCodec
sealed case class DAAudio(title: Option[String], performer: Option[String], waveform: Option[Array[Byte]], duration: Int) extends DocumentAttribute
#JsonCodec
sealed case class DAFilename(fileName: String) extends DocumentAttribute
object CirceEncodersDecoders {
import io.circe._
import io.circe.generic.extras._
import io.circe.generic.extras.semiauto._
implicit val arrayByteEncoder: Encoder[Array[Byte]] = Encoder.encodeString.contramap[Array[Byte]] { bytes ⇒
Base64.getEncoder.encodeToString(bytes)
}
val printer: Printer = Printer.noSpaces.copy(dropNullValues = true, reuseWriters = true)
implicit val config: Configuration = Configuration.default.withDiscriminator("kind").withSnakeCaseConstructorNames.withSnakeCaseMemberNames
implicit val DocumentAttributeEncoder: Encoder[DocumentAttribute] = deriveEncoder
implicit val DocumentAttributeDecoder: Decoder[DocumentAttribute] = deriveDecoder
}
object main {
def main(args: Array[String]): Unit = {
import CirceEncodersDecoders._
import io.circe.parser._
import io.circe.syntax._
val attributes: List[DocumentAttribute] = List(
DAAudio(Some("title"), Some("perform"), Some(List(1, 2, 3, 4, 5).map(_.toByte).toArray), 15),
DAFilename("filename")
)
val j2 = attributes.asJson
val decoded2 = decode[List[DocumentAttribute]](j2.noSpaces)
println(decoded2)
}
}
When you do this:
implicit val DocumentAttributeEncoder: Encoder[DocumentAttribute] = deriveEncoder
circe tries to get suitable Encoder for DAFilename and DAAudio. However, since those already exist (by means of #JsonCodec on individual classes), it does not re-derive them from scratch using generics and your Encoder[Array[Byte]] at scope - which you want.
So you can either get rid of #JsonCodec (so it auto-derives codecs for DAFilename and DAAudio together with DocumentAttribute) or trigger re-derivation manually:
implicit val AudioDecoder: Encoder[DAAudio] = deriveEncoder // takes priority over existing one
implicit val DocumentAttributeEncoder: Encoder[DocumentAttribute] = deriveEncoder // AudioDecoder will be used here
You also need to build a Decoder for Array[Byte] and repeat the process above for Decoders, otherwise it will try to parse Base64 string as a list of ints, resulting in a failure.
It seams that #JsonCodec annotations don't work with a custom encoder for Array[Byte].
Here is all the stuff that need for encoding and decoding of your classes with circe:
object CirceEncodersDecoders2 {
val printer: Printer = Printer.noSpaces.copy(dropNullValues = true, reuseWriters = true)
implicit val arrayByteEncoder: Encoder[Array[Byte]] =
Encoder.encodeString.contramap[Array[Byte]](Base64.getEncoder.encodeToString)
implicit val arrayByteDecoder: Decoder[Array[Byte]] =
Decoder.decodeString.map[Array[Byte]](Base64.getDecoder.decode)
implicit val config: Configuration = Configuration.default.withDiscriminator("kind").withSnakeCaseConstructorNames.withSnakeCaseMemberNames
implicit val audioEncoder: Encoder[DAAudio] = deriveEncoder[DAAudio]
implicit val audioDecoder: Decoder[DAAudio] = deriveDecoder[DAAudio]
implicit val filenameEncoder: Encoder[DAFilename] = deriveEncoder[DAFilename]
implicit val filenameDecoder: Decoder[DAFilename] = deriveDecoder[DAFilename]
implicit val documentAttributeEncoder: Encoder[DocumentAttribute] = deriveEncoder[DocumentAttribute]
implicit val documentAttributeDecoder: Decoder[DocumentAttribute] = deriveDecoder[DocumentAttribute]
}
If you are not limited in selection of JSON parser/serializer, then you can try a more efficient solution using jsoniter-scala.
DISCLAIMER: I'm an author of this library.
Here are results of benchmarks for both implementations:
[info] Benchmark Mode Cnt Score Error Units
[info] ListOfAdtWithBase64Benchmark.readCirce thrpt 5 114927.343 ± 7910.068 ops/s
[info] ListOfAdtWithBase64Benchmark.readJsoniterScala thrpt 5 1818299.170 ± 162757.404 ops/s
[info] ListOfAdtWithBase64Benchmark.writeCirce thrpt 5 117982.635 ± 8942.816 ops/s
[info] ListOfAdtWithBase64Benchmark.writeJsoniterScala thrpt 5 4281752.461 ± 319953.287 ops/s
Full sources are here.

Why does this getOrElse statement return type ANY?

I am trying to follow the tutorial https://www.jamesward.com/2012/02/21/play-framework-2-with-scala-anorm-json-coffeescript-jquery-heroku but of course play-scala has changed since the tutorial (as seems to be the case with every tutorial I find). I am using 2.4.3 This requires I actually learn how things work, not necessarily a bad thing.
One thing that is giving me trouble is the getOrElse method.
Here is my Bar.scala model
package models
import play.api.db._
import play.api.Play.current
import anorm._
import anorm.SqlParser._
case class Bar(id: Option[Long], name: String)
object Bar {
val simple = {
get[Option[Long]]("id") ~
get[String]("name") map {
case id~name => Bar(id, name)
}
}
def findAll(): Seq[Bar] = {
DB.withConnection { implicit connection =>
SQL("select * from bar").as(Bar.simple *)
}
}
def create(bar: Bar): Unit = {
DB.withConnection { implicit connection =>
SQL("insert into bar(name) values ({name})").on(
'name -> bar.name
).executeUpdate()
}
}
}
and my BarFormat.scala Json formatter
package models
import play.api.libs.json._
import anorm._
package object Implicits {
implicit object BarFormat extends Format[Bar] {
def reads(json: JsValue):JsResult[Bar] = JsSuccess(Bar(
Option((json \ "id").as[Long]),
(json \ "name").as[String]
))
def writes(bar: Bar) = JsObject(Seq(
"id" -> JsNumber(bar.id.getOrElse(0L)),
"name" -> JsString(bar.name)
))
}
}
and for completeness my Application.scala controller:
package controllers
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import javax.inject.Inject
import javax.inject._
import play.api.i18n.{ I18nSupport, MessagesApi, Messages, Lang }
import play.api.libs.json._
import views._
import models.Bar
import models.Implicits._
class Application #Inject()(val messagesApi: MessagesApi) extends Controller with I18nSupport {
val barForm = Form(
single("name" -> nonEmptyText)
)
def index = Action {
Ok(views.html.index(barForm))
}
def addBar() = Action { implicit request =>
barForm.bindFromRequest.fold(
errors => BadRequest,
{
case (name) =>
Bar.create(Bar(None, name))
Redirect(routes.Application.index())
}
)
}
def listBars() = Action { implicit request =>
val bars = Bar.findAll()
val json = Json.toJson(bars)
Ok(json).as("application/json")
}
and routes
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Home page
POST /addBar controllers.Application.addBar
GET / controllers.Application.index
GET /listBars controllers.Application.listBars
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
When I try to run my project I get the following error:
now bar.id is defined as an Option[Long] so bar.id.getOrElse(0L) should return a Long as far as I can tell, but it is clearly returning an Any. Can anyone help me understand why?
Thank You!
That's the way type inference works in Scala...
First of all there is an implicit conversion from Int to BigDecimal:
scala> (1 : Int) : BigDecimal
res0: BigDecimal = 1
That conversion allows for Int to be converted before the option is constructed:
scala> Some(1) : Option[BigDecimal]
res1: Option[BigDecimal] = Some(1)
If we try getOrElse on its own where the type can get fixed we get expected type Int:
scala> Some(1).getOrElse(2)
res2: Int = 1
However, this does not work (the problem you have):
scala> Some(1).getOrElse(2) : BigDecimal
<console>:11: error: type mismatch;
found : Any
required: BigDecimal
Some(1).getOrElse(2) : BigDecimal
^
Scala's implicit conversions kick in last, after type inference is performed. That makes sense, because if you don't know the type how would you know what conversions need to be applied. Scala can see that BigDecimal is expected, but it has an Int result based on the type of the Option it has. So it tries to widen the type, can't find anything that matches BigDecimal in Int's type hierarchy and fails with the error.
This works, however because the type is fixed in the variable declaration:
scala> val v = Some(1).getOrElse(2)
v: Int = 1
scala> v: BigDecimal
res4: BigDecimal = 1
So we need to help the compiler somehow - any type annotation or explicit conversion would work. Pick any one you like:
scala> (Some(1).getOrElse(2) : Int) : BigDecimal
res5: BigDecimal = 1
scala> Some(1).getOrElse[Int](2) : BigDecimal
res6: BigDecimal = 1
scala> BigDecimal(Some(1).getOrElse(2))
res7: scala.math.BigDecimal = 1
Here is the signature for Option.getOrElse method:
getOrElse[B >: A](default: ⇒ B): B
The term B >: A expresses that the type parameter B or the abstract type B refer to a supertype of type A, in this case, Any being the supertype of Long:
val l: Long = 10
val a: Any = l
So, we can do something very similar here with getOrElse:
val some: Option[Long] = Some(1)
val value: Any = option.getOrElse("potatos")
val none: Option[Long] = None
val elseValue: Any = none.getOrElse("potatos")
Which brings us to your scenario: the returned type from getOrElse will be a Any and not a BigDecimal, so you will need another way to handle this situation, like using fold, per instance:
def writes(bar: Bar) = {
val defaultValue = BigDecimal(0)
JsObject(Seq(
"id" -> JsNumber(bar.id.fold(defaultValue)(BigDecimal(_))),
"name" -> JsString(bar.name)
))
}
Some other discussions that can help you:
Why is Some(1).getOrElse(Some(1)) not of type Option[Int]?
Option getOrElse type mismatch error

Play Framework 2.3 Scala - Serialize nested objects to JSon with implicit Writes converters

I need for a frontend jquery-component a specific json object like this (ajax response):
[
{"division":"IT", "contacts":[
{“firstname”:”Carl”, “surname”:”Smith”, “empID”:1},
{“firstname”:”Henry”, “surname”:”Miller”, “empID”:2}
]},
{"division":"Sales", "contacts":[
{“firstname”:”Nancy”, “surname”:”McDonald”, “empID”:3},
{“firstname”:”Susan”, “surname”:”McBright”, “empID”:4}
]}
]
In Backend the data is read via anorm (MySQL) and transformed in following object:
List(Map("division" -> "IT", "contacts" -> List(c3,c4)), Map("division" -> "Sales", "contacts" -> List(c3,c4)))
Then I try to serialize the Object to JSon but without success (including implicit Writes converters). Below I made a simplified test case Idea-Worksheet in the same manner:
import play.api.libs.json.{JsValue, Json, Writes}
case class Contact(firstname: String, surname: String, empID: Option[Int])
case class ContactDivisionList(division: String, contacts: Seq[Contact])
implicit val ctWrites = new Writes[Contact] {
def writes(ct: Contact) = Json.obj(
"firstname" -> ct.firstname,
"surname" -> ct.surname,
"empid" -> ct.empID
)
}
implicit val sdlWrites = new Writes[ContactDivisionList] {
def writes(dlist: ContactDivisionList) = Json.obj(
"division" -> dlist.division,
"contacts" -> Json.toJson(dlist.contacts)
)
}
/*
Example
*/
val c1 = Contact("Carl","Smith",Option(1))
val c2 = Contact("Henry","Miller",Option(2))
val c3 = Contact("Nancy","McDonald",Option(3))
val c4 = Contact("Susan","McBright",Option(4))
//Test case 1 ->OK
Json.toJson(List(c1,c2,c3,c4))
//Test case 2 ->OK
val c_comp1=List(Map("contacts" -> List(c1,c2)),Map("contacts" -> List(c3,c4)))
//RESULT --> c_comp1: List[scala.collection.immutable.Map[String,List[Contact]]] = List(Map(contacts -> List(Contact(Carl,Smith,Some(1)), Contact(Henry,Miller,Some(2)))), Map(contacts -> List(Contact(Nancy,McDonald,Some(3)), Contact(Susan,McBright,Some(4)))))
Json.toJson(c_comp1)
//res1: play.api.libs.json.JsValue = [{"contacts": [{"firstname":"Carl","surname":"Smith","empid":1},{"firstname":"Henry","surname":"Miller","empid":2}]},{"contacts":[{"firstname":"Nancy","surname":"McDonald","empid":3},{"firstname":"Susan","surname":"McBright","empid":4}]}]
//Test case 3 ->Fail!!!
val c_comp2 = List(Map("division" -> "IT", "contacts" -> List(c1,c2)),Map("division" -> "Sales", "contacts" -> List(c3,c4)))
//sdlWrites: play.api.libs.json.Writes[ContactDivisionList]{def writes(dlist: ContactDivisionList): play.api.libs.json.JsObject} = $anon$2#3738baec
Json.toJson(c_comp2)
//!!!!!Error messages
/*Error:(39, 13) No Json serializer found for type List[scala.collection.immutable.Map[String,java.io.Serializable]]. Try to implement an implicit Writes or Format for this type.
Json.toJson(c_comp2)
^
Error:(39, 13) not enough arguments for method toJson: (implicit tjs: play.api.libs.json.Writes[List[scala.collection.immutable.Map[String,java.io.Serializable]] ])play.api.libs.json.JsValue.
Unspecified value parameter tjs.
Json.toJson(c_comp2)
^
*/
At the end of the script you can see "Test case 3" that got an error when i execute Json.toJson(c_comp2) -->"No Json serializer found for type..". I try a lot of things but i don't get it right. The only difference to successful "Test case 2" is that i extend the Map with a String-Tuppel.
I hope sombody can help me with that issue, Thx
Best regards
Karsten
Your problem is that the Map you have there is mapping String to the least upper bound of String (your division name) and List[Contact], which happens to be java.io.Serializable.
scala> case class Contact(firstname: String, surname: String, empID: Option[Int])
defined class Contact
scala> case class ContactDivisionList(division: String, contacts: Seq[Contact])
defined class ContactDivisionList
scala> val c1 = Contact("Carl","Smith",Option(1))
c1: Contact = Contact(Carl,Smith,Some(1))
scala> val c2 = Contact("Henry","Miller",Option(2))
c2: Contact = Contact(Henry,Miller,Some(2))
scala> val c3 = Contact("Nancy","McDonald",Option(3))
c3: Contact = Contact(Nancy,McDonald,Some(3))
scala> val c4 = Contact("Susan","McBright",Option(4))
c4: Contact = Contact(Susan,McBright,Some(4))
scala> Map("division" -> "IT", "contacts" -> List(c1,c2))
res10: scala.collection.immutable.Map[String,java.io.Serializable] = Map(division -> IT, contacts -> List(Contact(Carl,Smith,Some(1)), Contact(Henry,Miller,Some(2))))
I'm not entirely sure the nature of your problem, but if you already have List[ContactDivisionList], it's pretty straightforward to serialize that to JSON:
scala> implicit val contactWrites = Json.writes[Contact]
contactWrites: play.api.libs.json.OWrites[Contact] = play.api.libs.json.OWrites$$anon$2#3676af92
scala> implicit val contactDivisionListWrites = Json.writes[ContactDivisionList]
contactDivisionListWrites: play.api.libs.json.OWrites[ContactDivisionList] = play.api.libs.json.OWrites$$anon$2#2999d17d
scala> Json.toJson(List(ContactDivisionList("IT", List(c1,c2)), ContactDivisionList("Sales", List(c3, c4))))
res2: play.api.libs.json.JsValue = [{"division":"IT","contacts":[{"firstname":"Carl","surname":"Smith","empID":1},{"firstname":"Henry","surname":"Miller","empID":2}]},{"division":"Sales","contacts":[{"firstname":"Nancy","surname":"McDonald","empID":3},{"firstname":"Susan","surname":"McBright","empID":4}]}]
It seems to me that you should avoid having that Map in the first place. I've never worked with anorm before, but I think where you need to be looking at is the code that data structure, because at that point, you've lost typesafety. You should ideally work with ContactDivisionList or construct your object using the JsValue cases directly.

Scala play json api: can't implicitly convert JsArray to JsValueWrapper

I have json rest api application based on play framework and want to receive information about validation errors when I parse incoming requests. Everything is working fine except conversion from json array to json value.
Json structure I want to achieve:
{
"errors": {
"name": ["invalid", "tooshort"],
"email": ["invalid"]
}
}
When I tried to implement a sample it worked perfectly:
def error = Action {
BadRequest(obj(
"errors" -> obj(
"name" -> arr("invalid", "tooshort"),
"email" -> arr("invalid")
)
))
}
When I tried to extract the changing part like this:
def error = Action {
val e = Seq("name" -> arr("invalid", "tooshort"), "email" -> arr("invalid"))
// in real app it will be Seq[(JsPath, Seq[ValidationError])]
BadRequest(obj(
"errors" -> obj(e: _*)
))
}
I got a compiler error:
type mismatch; found : Seq[(String, play.api.libs.json.JsArray)] required: Seq[(String, play.api.libs.json.Json.JsValueWrapper)]
Maybe there is some implicit conversion I'm missing from JsArray to JsValueWrapper? But then, why does the sample works fine in the same file with the same imports?
Play 2.1.1, Scala 2.10.0
UPDATE:
Problem resolved thanks to Julien Lafont, the final code:
implicit val errorsWrites = new Writes[Seq[(JsPath, Seq[ValidationError])]] {
/**
* Maps validation result of Ember.js json request to json validation object, which Ember can understand and parse as DS.Model 'errors' field.
*
* #param errors errors collected after json validation
* #return json in following format:
*
* {
* "errors": {
* "error1": ["message1", "message2", ...],
* "error2": ["message3", "message4", ...],
* ...
* }
* }
*/
def writes(errors: Seq[(JsPath, Seq[ValidationError])]) = {
val mappedErrors = errors.map {
e =>
val fieldName = e._1.toString().split("/").last // take only last part of the path, which contains real field name
val messages = e._2.map(_.message)
fieldName -> messages
}
obj("errors" -> toJson(mappedErrors.toMap)) // Ember requires root "errors" object
}
}
Usage:
def create = Action(parse.json) { // method in play controller
request =>
fromJson(request.body) match {
case JsSuccess(pet, path) => Ok(obj("pet" -> Pets.create(pet)))
case JsError(errors) => UnprocessableEntity(toJson(errors)) // Ember.js expects 422 error code in case of errors
}
}
You can simply define your errors in a Map[String, Seq[String]], and transform it in Json with Json.toJson (there are built-in writers for Map[String,Y] and Seq[X])
scala> val e = Map("name" -> Seq("invalid", "tooshort"), "email" -> Seq("invalid"))
e: scala.collection.immutable.Map[String,Seq[String]] = Map(name -> List(invalid, tooshort), email -> List(invalid))
scala> Json.toJson(e)
res0: play.api.libs.json.JsValue = {"name":["invalid","tooshort"],"email":["invalid"]}
scala> Json.obj("errors" -> Json.toJson(e))
res1: play.api.libs.json.JsObject = {"errors":{"name":["invalid","tooshort"],"email":["invalid"]}}
The reason the long handed version works is because scala's automatic type inference triggers an implicit conversion toJsFieldJsValueWrapper.
For example
scala> import play.api.libs.json._
import play.api.libs.functional.syntax._
import play.api.libs.json._
scala> import play.api.libs.functional.syntax._
import play.api.libs.functional.syntax._
scala> import Json._
import Json._
scala> arr("invalid", "tooshort")
res0: play.api.libs.json.JsArray = ["invalid","tooshort"]
scala> obj("name" -> arr("invalid", "tooshort"))
res1: play.api.libs.json.JsObject = {"name":["invalid","tooshort"]}
scala> obj _
res3: Seq[(String, play.api.libs.json.Json.JsValueWrapper)] => play.api.libs.json.JsObject = <function1>
Notice that arr returns a JsArray, however obj requires a JsValueWrapper. Scala is able to convert the JsArray to JsValueWrapper when it constructs the arguments for obj. However it is not able to convert a Seq[(String, JsArray)] to a `Seq[(String, JsValueWrapper)].
If you provide the expected type when of the sequence in advance, the Scala compiler's type inferencer will perform the conversion as it creates the sequence.
scala> Seq[(String, JsValueWrapper)]("name" -> arr("invalid", "tooshort"), "email" -> arr("invalid"))
res4: Seq[(String, play.api.libs.json.Json.JsValueWrapper)] = List((name,JsValueWrapperImpl(["invalid","tooshort"])), (email,JsValueWrapperImpl(["invalid"])))
However once the sequence is created it cannot be converted unless there is an implicit conversion in scope that can convert sequences.
The final code snippet looks like:
def error = Action {
val e = Seq[(String, JsValueWrapper)]("name" -> arr("invalid", "tooshort"), "email" -> arr("invalid"))
// in real app it will be Seq[(JsPath, Seq[ValidationError])]
BadRequest(obj(
"errors" -> obj(e: _*)
))
}

toJson(Map("success"->true, "message"->str)) can't be compiled in play2

This is my code:
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.json.Json._
import play.api.libs.json.Writes._
class BaseController extends Controller with Secured with DefaultWrites {
private implicit def str2json(str: String) = new {
def asSuccessJson = toJson(Map("success" -> true, "message" -> str)) // (*)
def asFailedJson = toJson(Map("success" -> false, "message" -> str)) // (*)
}
}
But it can't be compiled on two (*) lines. The error message is:
Multiple markers at this line
- No Json deserializer found for type scala.collection.immutable.Map[java.lang.String,Any]. Try
to implement an implicit Writes or Format for this type.
- not enough arguments for method toJson: (implicit tjs:
play.api.libs.json.Writes[scala.collection.immutable.Map[java.lang.String,Any]])
play.api.libs.json.JsValue.Unspecified value parameter tjs.
I have to write it as:
def asSuccessJson = toJson(Map("success" -> true.toString, "message" -> str))
Notice true.toString. It works but boring.
How to fix it?
It's quite logical: you try to convert an heterogeneous map into a JsValue:
Map("success" -> true, "message" -> str) is a Map[String, Any].
There is no implicit writer able to convert a Map[String, Any] into a JsValue (and there can't be any).
When you write Map("success" -> true.toString, "message" -> str), you create a Map[String, String] and there is a writer for this.
I would write:
def asSuccessJson = JsObject(Seq("success" -> JsBoolean(true), "message" -> JsString(str))) // (*)
BTW, the JSON API will certainly be "beautified" a bit in Play 2's next releases...