I started using the Playframework recently and am implementing a site using Play 2.1.1 and Slick 1.0.0. I'm now trying to wrap my head around Json Writes as I want to return Json in one of my controllers.
I've been looking at several references on the subject (like this one and this one but can't figure out what I'm doing wrong.
I have a model looking like this:
case class AreaZipcode( id: Int,
zipcode: String,
area: String,
city: String
)
object AreaZipcodes extends Table[AreaZipcode]("wijk_postcode") {
implicit val areaZipcodeFormat = Json.format[AreaZipcode]
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def zipcode = column[String]("postcode", O.NotNull)
def area = column[String]("wijk", O.NotNull)
def city = column[String]("plaats", O.NotNull)
def autoInc = * returning id
def * = id ~ zipcode ~ area ~ city <> (AreaZipcode.apply _, AreaZipcode.unapply _)
}
You can see the implicit val which I'm trying to use, but when I try to return the Json in my controller by doing this:
Ok(Json.toJson(areas.map(a => Json.toJson(a))))
I'm somehow still confronted with this errormessage:
No Json deserializer found for type models.AreaZipcode. Try to implement an implicit Writes or Format for this type.
I've tried several other ways to implement the Writes. For instance, I've tried the following instead of the implicit val from above:
implicit object areaZipcodeFormat extends Format[AreaZipcode] {
def writes(a: AreaZipcode): JsValue = {
Json.obj(
"id" -> JsObject(a.id),
"zipcode" -> JsString(a.zipcode),
"area" -> JsString(a.area),
"city" -> JsString(a.city)
)
}
def reads(json: JsValue): AreaZipcode = AreaZipcode(
(json \ "id").as[Int],
(json \ "zipcode").as[String],
(json \ "area").as[String],
(json \ "city").as[String]
)
}
Can someone please point me in the right direction?
JSON Inception to the rescue! You only need to write
import play.api.libs.json._
implicit val areaZipcodeFormat = Json.format[AreaZipcode]
That's it. No need to write your own Reads and Writes anymore, thanks to the magic of Scala 2.10 macros. (I recommend that you read Play's documentation on Working with JSON, it explains a lot.)
Edit:
I didn't notice you already had the Json.format inside the AreaZipcodes object. You either need to move that line out of AreaZipcodes or import it into your current context, i.e.
import AreaZipcodes.areaZipcodeFormat
Related
I am building HTTP service with scala-play. I want to serve the JSON response.
To serve JSON response, I created case classes and created instance of Writes[T] class which is similar to following
case class Point(X: Double, Y: Double)
implicit val pointWrites = new Writes[Point] {
def writes(point: Point) = Json.obj(
"X" -> point.X,
"Y" -> point.Y
)
}
I can server response like: val json = Json.toJson(Point(10, 20))
I followed above approach from play documentation.
Why do I need to define class and create Writes[T] instance for serve JSON response.
If I have to create JSON for dozens of classes, do I have to define all classes and create instances for Writes[T]?
I can easily convert any class data to JSON with following method:
def getJSON(data: Map[String, String]): JsObject = {
JsObject(data.map { el => el._1 -> JsString(el._2) })
}
All I need do is create Map[String, String] for any class to serve JSON.
Why play documentation recommends to define case class and create instance of Writes[T] to create JSON representation?
Is there any other way to serve create JSON in scala? I want to keep response time minimal so that JSON creation must be lightening fast.
You don't need to create the Writes instance. Play already have a default formatter for all the regular types (Integer/String/Boolean..)
You just need to write the case class and add an implicit formatter in the companion object.
See json.scala format[A] description from play:
/**
* Creates a Format[T] by resolving case class fields & required implicits at COMPILE-time
*
* If any missing implicit is discovered, compiler will break with corresponding error.
* {{{
* import play.api.libs.json.Json
*
* case class User(name: String, age: Int)
*
* implicit val userWrites = Json.format[User]
* // macro-compiler replaces Json.format[User] by injecting into compile chain
* // the exact code you would write yourself. This is strictly equivalent to:
* implicit val userWrites = (
* (__ \ 'name).format[String] and
* (__ \ 'age).format[Int]
* )(User.apply, unlift(User.unapply))
* }}}
*/
def format[A] = macro JsMacroImpl.formatImpl[A]
Back to your example, you only need:
case class Point(X: Double, Y: Double)
object Point{
implicit val fmtJson = Json.format[Point]
}
to get the json text just do:
val json = Point.fmtJson.writes(Point(10, 20)).toString()
will result: json = {"X":10,"Y":20}
I'm a newbie when it comes to Scala and right now trying to learn Slick. I have a database set up much like in the Slick tutorial and querying it with
val users:TableQuery[user] = TableQuery[user]
How do I output users -value in to JSON from Play framework? I have tried defining case class for user and making reader+writer for userFormat = Json.format[user] but I have no clue where I'm going wrong or anything. I've done similar kind of stuff with Spray.IO and it seemed simple and straightforward there, but I want to learn how to do it with Play and Slick and I am completely lost :(
Here is my Tables.scala:
package assets
import org.joda.time.DateTime
import play.api.libs.json._
import scala.slick.driver.MySQLDriver.simple._
import com.github.tototoshi.slick.MySQLJodaSupport._
import scala.slick.lifted.{ProvenShape}
case class user
(
id:Int,
textId:String,
sessionId:String,
stamp:DateTime
)
class users(tag: Tag) extends Table[(Int, String, String, DateTime)](tag, "USER") {
implicit object userFormat extends Format[user] {
def writes(a: user): JsValue = {
Json.obj(
"id" -> JsNumber(a.id),
"textId" -> JsString(a.textId),
"sessionId" -> JsString(a.sessionId),
"stamp" -> JsString(a.stamp.toString())
)
}
def reads(json: JsValue): user = user(
(json \ "id").as[Int],
(json \ "textId").as[String],
(json \ "sessionId").as[String],
(json \ "stamp").as[DateTime]
)
}
def id: Column[Int] = column[Int]("id", O.PrimaryKey)
def textId: Column[String] = column[String]("textId")
def sessionId: Column[String] = column[String]("sessionId")
def stamp: Column[DateTime] = column[DateTime]("stamp")
def * : ProvenShape[(Int, String, String, DateTime)] = (id,textId,sessionId,stamp)
}
First of all, please add some context to your question as it's very hard to see what is not working (e.g. do you get compilation errors? Do you get exceptions?).
With respect to the question content, you should think of getting data from slick and translating it into JSON as two totally independent steps; in fact, there is nothing in slick that will help you in making JSON out of your case classes.
Given that, you will need to run your slick queries and get case class instances for your types out of them, e.g. you should get a Seq[user]. To convert that to JSON you should invoke Json.toJson(myUserSeq). There is one thing that will probably get in your way for what I can see in your code: to have the Format correctly in scope you should put it in the companion object of your case class, e.g.:
case class User(...)
object User {
implicit object format extends Format[User] { ... }
}
As a sideline, please be aware of the language's coding standards if you want people to help you and your code to be readable (e.g. having lower-case class names in your code makes it very hard to follow).
For example suppose I have
case class Test(a: String, b: String)
...
implicit val testFormat = jsonFormat2(Test.apply)
and a json with an extra c field:
val test = "{\"a\": \"A\", \"b\": \"B\", \"c\": \"C\"}"
then I want to find a way (config/param/whatever) to make the following line throw and exception:
test.parseJson.convertTo[Test]
It's very hard to work this out from reading the source code and github documentation.
I didn't see anything in the library that provides that functionality, so I created a wrapper that does a quick check on the number of fields present before delegating the read call to the supplied formatter.
case class StrictRootFormat[T](format: RootJsonFormat[T], size: Int) extends RootJsonFormat[T] {
def read(json: JsValue): T = {
if(json.asJsObject.fields.size > size) deserializationError("JSON has too many fields: \n " + json.toString())
else format.read(json)
}
def write(obj: T): JsValue = format.write(obj)
}
Usage:
implicit val testFormat = StrictRootFormat(jsonFormat2(Test.apply), 2)
You could enhance the read implementation so that you don't need to supply the "size" argument.
I am learning more and more about Scala and that nice playframework. But there are some things that bother me and that I can't get to work.
I like using Generics for some kind of collections, for example. But I need those to be stored in our database, in JSON. There is this cool auto conversion thing, but it does not work for generics, in no way I have tried :-/
Okay, to be concrete, code first:
case class InventorySlot(id: Long, item: Option[Item])
object InventorySlot {
implicit val fmt = Json.format[InventorySlot]
}
case class Inventory[T <: Item](slots: Vector[InventorySlot]) {
def length = slots.length
def items: Vector[T] = slots.map(slot => slot.item).flatten.asInstanceOf[Vector[T]]
def item(id: Long): Option[T] = {
slots.find(_.id == id) match {
case Some(slot: InventorySlot) =>
Some(slot.item.asInstanceOf[T])
case None =>
Logger.warn(s"slot with id $id not found")
None
}
}
}
object Inventory {
implicit val fmt = Json.format[Inventory]
}
Item is a basic abstract class of different items that can be put in that inventory. It doesn't matter. But sometimes I want to have an inventory, that just works for ItemType A, lets call it AItem.
So I want to create my inventory with something like this:
val myInventory = Inventory[AItem]("content of vector here") and when I call myInventory.item(2), then I want to get the item in slot 2, and it should be an object of type AItem, not just Item. (That's the reason why I am using generics here)
So the problem
The implicit format for Inventory does not work, obviously.
Item does, also with all special items, I can post the code for it below, and InventorySlot should work as well.
The error when compiling is:
Error:(34, 34) Play 2 Compiler:
C:\depot\mars\mainline\server\app\models\Test.scala:34: class Inventory takes type parameters
implicit val fmt = Json.format[Inventory]
^
I tried to write the read and write explicitly, like
implicit val fmt = (
(__ \ "slots").format[Vector[InventorySlot]]
)(Inventory.apply, unlift(Inventory.unapply))
wich is not even working in my IDE, and I can't find the problem.
I am confused. I don't know where my error lies, or if I am doing something wrong, or if I just miss something.
Any help will be appreciated.
I am so helpless, I even have considered doing a class for all possible inventory types, like
case class AItemInventory(protected var slots: Vector[InventorySlot]) extends Inventory[AItem](slots)
object AItemInventory {
implicit val fmt = Json.format[AItemInventory]
}
wich works. No problems, everything fine. So... I don't understand. Why is this working if it seems to be exactly the same, just hardcoded?
Appendix
The item formatter, wich works:
implicit val itemFormat = new Format[Item] {
override def reads(json: JsValue): JsResult[Item] = {
(json \ "itemType").as[ItemType] match {
case ItemType.AITEM => fmtAItem.reads(json)
}
}
override def writes(item: Item): JsValue = item match {
case subItem: AItem => fmtAItem.writes(subItem)
case _ => JsNumber(item.itemType.id)
}
}
object Inventory {
implicit def fmt[T <: Item](implicit fmt: Format[T]): Format[Inventory[T]] = new Format[Inventory[T]] {
def reads(json: JsValue): Inventory[T] = new Inventory[T] (
(json \ "blah").as[String]
)
def writes(i: Inventory[T]) = JsObject(Seq(
"blah" -> JsString(i.blah)
))
}
}
Source: documentation explains how to do it for reads and writes, and what I've done here is to combine these two for the format.
This is my Search Object:
package models.helper
import play.api.libs.json.Format
import play.api.libs.json.JsValue
import play.api.libs.json.JsObject
import play.api.libs.json.JsString
case class Search (name: String, `type`:String){
implicit object SearchFormat extends Format[Search] {
def reads(json: JsValue): Search = Search(
(json \ "name").as[String],
(json \ "type").as[String]
)
def writes(s: Search): JsValue = JsObject(Seq(
"name" -> JsString(s.name),
"type" -> JsString(s.`type`)
))
}
}
I'm trying ot use this class when calling a webservice using WS:
val search = response.json.as[Search]
But the scala compiler keeps complaining on this line:
No Json deserializer found for type models.helper.Search. Try to
implement an implicit Reads or Format for this type.
Could anybody tell me what I'm doing wrong?
got the example from https://sites.google.com/site/play20zh/scala-developers/working-with-json
this thread discusses the same issue but gives no solution, what example on what site? https://groups.google.com/forum/?fromgroups#!topic/play-framework/WTZrmQi5XxY
Indeed the example is wrong. You need your implicit Format[Search] value to be available in the implicit scope.
In your case the Format[Search] is defined as a nested value of the class Search, so you can reach it only from an instance of Search.
So, what you want to do is to define it in another place, where it could be referenced without having to create an instance of Search, e.g. in a Formats object:
object Formats {
implicit SearchFormat extends Format[Search] {
…
}
}
Then you can use it as follows:
import Formats.SearchFormat
val search = response.json.as[Search]
You can also get rid of the import tax by defining the Format[Search] value in the companion object of the Search class. Indeed the Scala compiler automatically looks in companion objects of type parameters when it needs an implicit value of a given type:
case class Search(name: String, `type`: String)
object Search {
implicit object SearchFormat extends Format[Search] {
…
}
}
Then you can use it without having to import it:
val search = response.json.as[Search]