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

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: _*)
))
}

Related

JSON Parser for Scala 2.12

I am looking for a way to re-write the following code using a JSON parser. This is currently using scala.util.parsing.json.JSONObject which has been deprecated.
val notebookInfo = Map("notebookURL" -> notebookURL,
"user" -> user,
"name" -> name,
"mounts" -> dbutils.fs.ls("/mnt").map(_.path),
"timestamp" -> System.currentTimeMillis)
val notebookInfoJson = scala.util.parsing.json.JSONObject(notebookInfo)
I have tried the following with Circe JSON parser but getting errors:
val notebookInfo = Json.obj("notebookURL" -> Json.fromString(notebookURL),
"user" -> Json.fromString(user),
"name" -> Json.fromString(name),
"mounts" -> Json.arr(Seq(dbutils.fs.ls("/mnt").map(Json.fromString) :_*),
"timestamp" -> Json.fromLong(System.currentTimeMillis)))
val notebookInfoJson = notebookInfo
Here are the errors I am seeing:
error: type mismatch;
found : String => io.circe.Json
required: com.databricks.backend.daemon.dbutils.FileInfo => ?
"mounts" -> Json.arr(Seq(dbutils.fs.ls("/mnt").map(Json.fromString) :_*),
^
error: type mismatch;
found : (String, io.circe.Json)
required: io.circe.Json
"timestamp" -> Json.fromLong(System.currentTimeMillis)))
Please go through this article. It has few examples with JSON parsing frameworks that could be used with Scala.
import net.liftweb.json.JsonAST
import net.liftweb.json.JsonDSL._
import net.liftweb.json.Printer.{compact,pretty}
object LiftJsonWithCollections extends App {
val json = List(1, 2, 3)
println(compact(JsonAST.render(json)))
val map = Map("fname" -> "Alvin", "lname" -> "Alexander")
println(compact(JsonAST.render(map)))
}
That program prints the following output:
[1,2,3]
{"fname":"Alvin","lname":"Alexander"}
If needed also have a look on this one for creating a JSON String from Scala classes that have collections.

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

parse json string to a scala object

I use play framework version 2.2.1 with scala.
I have a large json-string that i wish to construct an object from.
I have written the formatters for the classes.
My question is what are the steps i am supposed to cast that string to concrete class.
e.g of code that i've tried:
val something = scala.util.parsing.json.JSON.parseFull(jsonContent).asInstanceOf[HsmTenant] //This has stucked my debug session and operation never completes
Or
val concreteClass = Json.parse(jsonContent).asInstanceOf[T] //message: "Error processing request - Failed to migrate, Exception=play.api.libs.json.JsObject cannot be cast to migration.hsm.HsmTenant"
When trying:
Json.parse(jsonContent).as[T]
I get the following:
Multiple markers at this line
- No Json deserializer found for type migration.hsm.HsmTenant. Try to implement an implicit Reads or Format for this type.
- not enough arguments for method as: (implicit fjs: play.api.libs.json.Reads[migration.hsm.HsmTenant])migration.hsm.HsmTenant. Unspecified value
parameter fjs.
- No Json deserializer found for type migration.hsm.HsmTenant. Try to implement an implicit Reads or Format for this type.
- not enough arguments for method as: (implicit fjs: play.api.libs.json.Reads[migration.hsm.HsmTenant])migration.hsm.HsmTenant. Unspecified value
parameter fjs.
- Line breakpoint:HsmParser [line: 16] - parse(jsonContent: String): migration.hsm.HsmTenant
My read and writes looks like: and they compile:
trait HsmFormats {
implicit val hsmRetailerFormat = Json.format[Retailer]
implicit val hsmproductFormat = new Format[HsmProduct]
{
def writes(item: HsmProduct): JsValue =
{
val retailers = item.r.getOrElse(List[Retailer]())
val images = item.images.getOrElse(List[String]())
Json.obj(
"u" -> item.u,
"s" -> item.s,
"z" -> item.z,
"n" -> item.n,
"v" -> item.v,
"vu" -> item.vu,
"t" -> item.t,
"r" -> retailers,
"images" -> images
)
}
def reads(json: JsValue): JsResult[HsmProduct] =
{
val retailers = (json \ "r").as[Option[List[Retailer]]]
var retaliersReal:Option[List[Retailer]] = None
if (retailers.isDefined)
{
retaliersReal = Some(retailers.get)
}
val images = (json \ "images").as[Option[List[String]]]
var imagesReal:Option[List[String]] = None
if (images.isDefined)
{
imagesReal = Some(images.get)
}
JsSuccess(new HsmProduct(
(json \ "u").as[String],
(json \ "s").as[Int],
(json \ "z").as[Int],
(json \ "n").as[String],
(json \ "v").as[String],
(json \ "vu").as[String],
(json \ "t").as[String],
retailers,
imagesReal
))
}
}
implicit val hsmTenantFormat = new Format[HsmTenant]
{
def writes(tenant: HsmTenant): JsValue =
{
val items = tenant.items.getOrElse(List[HsmProduct]())
Json.obj(
"items" -> tenant.items,
"prefixAndroid" -> tenant.prefixAndroid,
"prefixIOS" -> tenant.prefixIOS,
"er" -> tenant.er,
"erMessage" -> tenant.erMessage
)
}
def reads(json: JsValue): JsResult[HsmTenant] =
{
val items = (json \ "items").as[Option[List[HsmProduct]]]
var itemsReal:Option[List[HsmProduct]] = None
if (items.isDefined)
{
itemsReal = Some(items.get)
}
JsSuccess(new HsmTenant(
itemsReal,
(json \ "prefixAndroid").as[String],
(json \ "prefixIOS").as[String],
(json \ "er").as[Int],
(json \ "erMessage").as[String]
))
}
}
This should work if your Formats are correct:
import play.api.libs.json.Json
Json.parse(jsonContent).as[T]
What was the problem ?
It was not issue if import the Formats class, my format is a trait, so i must extend that trait in classes i use it.

Automatic serialization/deserialization of generic case classes to/from JSON in Play2.2 Scala

This question is based on the following example, which is an attempt to deserialize a case class Node[Bird] from JSON.
import play.api.libs.json._
import play.api.libs.functional.syntax._
object ValidationTest {
case class Node[T](_id: Int, thing: T)
case class Bird(name: String, color: String)
// ERROR -- No apply function found matching unapply parameters
implicit val birdNodeFormat = Json.format[Node[Bird]]
val birdNodesJson: JsValue = Json.arr(
Json.obj(
"_id" -> 0,
"thing" -> Json.obj(
"name" -> "Cardinal",
"color" -> "Red"
)
),
Json.obj(
"_id" -> 1,
"thing" -> Json.obj(
"name" -> "Bluejay",
"color" -> "Blue"
)
)
)
val birdNodesResult = birdNodesJson.validate[Seq[Node[Bird]]]
}
In the preceding example, Scala is unable to resolve the proper apply/unapply functions for Node[Bird] for the format macro.
// ERROR -- No apply function found matching unapply parameters
implicit val birdNodeFormat = Json.format[Node[Bird]]
However, there is no problem with using a non-generic case class such as BirdNode.
case class BirdNode(_id: Int, thing: Bird)
// WORKS
implicit val birdNodeFormat = Json.format[BirdNode]
...
// WORKS
val birdNodesResult = birdNodesJson.validate[Seq[BirdNode]]
What is the best way to serialize/deserialize something like a Node[Bird] to/from JSON in Play 2.2?
You might have to define the format for Node[T] without using the macro, but this works:
implicit val birdFormat: Format[Bird] = Json.format[Bird]
implicit def nodeFormat[T : Format]: Format[Node[T]] =
((__ \ "_id").format[Int] ~
(__ \ "thing").format[T]
)(Node.apply, unlift(Node.unapply))

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...