How could I properly work with Scala Play Read? - json

According to this documentation (official):
https://www.playframework.com/documentation/2.8.x/ScalaJsonCombinators
I have to create a case class, after that I have to create a JsonReader:
val nameReads: Reads[String] = (JsPath \ "name").read[String]
then
val nameResult: JsResult[String] = json.validate[String](nameReads)
So, the result would be into nameResult and it was expecting that data was accessible like this:
println(nameResult.name)
Unfortunately, it doesn't work. It doesn't print results or return them.
First of all I work with Future and read JSON from web
implicit val context = scala.concurrent.ExecutionContext.Implicits.global
val userReads: Reads[User] = (
(JsPath \ "id").read[Int] and
(JsPath \ "login").read[String]
)
val futureResult = wc.url(path).get().map {
response =>
response.json.validate[User](userReads)
}
futureResult.map(r => println(r.id, r.login))
But! This code works, but it isn't in documentation.
implicit val context = scala.concurrent.ExecutionContext.Implicits.global
val userReads: Reads[User] = (
(JsPath \ "id").read[Int] and
(JsPath \ "login").read[String]
)
val futureResult = wc.url(path).get().map {
response =>
UserTest(
(response.json \ "id").as[String],
(response.json \ "login").as[String]
)
}
futureResult.map(r => println(r.id, r.login))
Does somebody know why code into documentation doesn't work? What is wrong with it?
Could I use my code?

Calling validate[User] doesn't return a User but a JsResult[User]. This is because the JSON data might be invalid and your code needs to handle this case. There is an example in the documentation that you have linked to:
json.validate[Place] match {
case JsSuccess(place, _) => {
val _: Place = place
// do something with place
}
case e: JsError => {
// error handling flow
}
}

Related

Scala Graph JSON with Play Framework

I am trying to pass in a POST request to a REST API developped with Play! (2.5) an object that I would like to use a Scala Graph (from the graph-core dependency).
It looks like the graph already has JSON serialization/deserialization methods based on lift-json, but I am not sure how to "plug" that into Play Json library. Until now I was using implicit converters (with Reads/Writes methods) but I would like to avoid having to write my own methods for the graph part since it is already part of the library itself.
For instance, let's say I have this code:
import java.util.UUID
import scalax.collection.Graph
case class Task(
id: UUID,
status: String)
case class Stuff(
id: UUID = UUID.randomUUID(),
name: String,
tasks: Option[Graph[Task, DiEdge]])
implicit val stuffWrites: Writes[Stuff] = (
(JsPath \ "id").write[UUID] and
(JsPath \ "name").write[String]
)(unlift(Stuff.unapply))
implicit val stuffReads: Reads[Stuff] = (
(JsPath \ "id").read[UUID] and
(JsPath \ "name").read[String]
)(Stuff.apply _)
implicit val taskWrite: Writes[Task] = (
(JsPath \ "id").write[UUID] and
(JsPath \ "status").write[String]
)(unlift(Task.unapply))
implicit val taskReads: Reads[Task] = (
(JsPath \ "id").read[UUID] and
(JsPath \ "status").read[String]
)(Task.apply _)
I miss the part to serialize the graph and the parenting. Should I rewrite everything from scratch, or can I rely on methods toJson/fromJson from scalax.collection.io.json ?
Since I struggled a bit to get this working, I thought I would share the code:
class UUIDSerializer extends Serializer[UUID] {
private val UUIDClass = classOf[UUID]
def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), UUID] = {
case (TypeInfo(UUIDClass, _), json) => json match {
case JString(id) => UUID.fromString(id)
case x => throw new MappingException("Can't convert " + x + " to UUID")
}
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case x: UUID => JString(x.toString)
}
}
val extraSerializers = new UUIDSerializer :: Nil
implicit val formats = Serialization.formats(NoTypeHints) ++ extraSerializers
val taskDescriptor = new NodeDescriptor[Task](typeId = "Tasks", customSerializers=extraSerializers) {
def id(node: Any) = node match {
case Task(id, _) => id.toString
}
}
val quickJson = new Descriptor[Task](
defaultNodeDescriptor = taskDescriptor,
defaultEdgeDescriptor = Di.descriptor[Task]()
)
implicit val tasksWrites = new Writes[Graph[Task, DiEdge]] {
def writes(graph: Graph[Task, DiEdge]): JsValue = {
val json = graph.toJson(quickJson)
Json.parse(json.toString)
}
}
implicit val tasksReads = new Reads[Graph[Task, DiEdge]] {
def reads(json: JsValue): JsResult[Graph[Task, DiEdge]] = {
try {
val graph = Graph.fromJson[Task, DiEdge](json.toString, quickJson)
JsSuccess(graph)
}
catch {
case e: Exception =>
JsError(e.toString)
}
}
}
implicit def stuffModelFormat = Jsonx.formatCaseClass[Stuff]
You can try writing companion objects for yours case classes where you specify the formatting.
Example:
object Task {
implicit val taskModelFormat = Json.format[Task]
}
object Stuff {
implicit val staffModelFormat = Json.format[Stuff]
}
instead of the above implicits. With this solution compiler will resolve the known formatters for you and you could be only required to specify the missing/unknown types instead of the whole structure.

How to convert Future[Seq[SomeClass]] to Json?

In Play framework I'm using Slick with MySQL database, how could I convert query result (Future[Seq[SomeClass]]) to Json for further usage in jQuery Autocomplete. I can serialize SomeClass , but where should I use .map(or something else) ?
Added:
Model:
package models
import play.api.libs.json._
case class Equipment(id: Long, name: String,area: String,kiMin: Double,kiMax: Double,cosFiMin: Double,cosFiMax: Double){
implicit val equipmentWrites = new Writes[Equipment] {
def writes(equipment: Equipment) = Json.obj(
"id" -> equipment.id,
"name" -> equipment.name,
"area" -> equipment.area,
"kiMin" -> equipment.kiMin,
"kiMax" -> equipment.kiMax,
"cosFiMin" -> equipment.cosFiMin,
"cosFiMax" -> equipment.cosFiMax
)
//also tried this for Seq
/* def writes(equipment: Equipment): JsValue = {
val equipmentSeq = Seq(
"id" -> JsNumber(equipment.id),
"name" -> JsString(equipment.name),
"area" -> JsString(equipment.area),
"kiMin" -> JsNumber(equipment.kiMin),
"kiMax" -> JsNumber(equipment.kiMax),
"cosFiMin" -> JsNumber(equipment.cosFiMin),
"cosFiMax" -> JsNumber(equipment.cosFiMax)
)
JsObject(equipmentSeq)
}*/
}
}
Controller:
def auto(term: String) = Action {
Ok(Json.toJson(equipmentDAO.get(term)))
}
DAO:
def get(name: String): Future[Seq[Equipment]] = db.run((equipment.filter { _.name === name }).result)
Added2:
controller method:
def auto(term: String) = Action.async {
val future: Future[Seq[Equipment]] = equipmentDAO.get(term)
future.map { seqOfSomeClass =>
Ok(Json.toJson(seqOfSomeClass))
}
}
Serializer:
implicit val equipmentWrites: Writes[Equipment] = (
(JsPath \ "id").write[Long] and
(JsPath \ "name").write[String] and
(JsPath \ "area").write[String] and
(JsPath \ "kiMin").write[Double] and
(JsPath \ "kiMax").write[Double] and
(JsPath \ "cosFiMin").write[Double] and
(JsPath \ "cosFiMax").write[Double]
)(unlift(Equipment.unapply))
Considering that you understand how Play Framework handles JSON and already have Reads and Writes for SomeClass, you can do something like this in your controller:
def someAction = Action.async {
val future: Future[Seq[SomeClass]] = ??? // get the future using Slick
future.map { seqOfSomeClass =>
Ok(Json.toJson(seqOfSomeClass))
}
}
This will serve a list of SomeClass in JSON.

getOrElse Scala Play JSON Payload

I am trying to not only clean this code up, but it is breaking when the request.body isEmpty, which defeats the purpose of this code segment. I have been referring to Play's documentation to implement getOrElse, but I'm having a hard time trying to see how I can turn my code segment into what they've provided. Here is the method:
override def createWOTC(clientID: Int) =
Action(parse.json) { implicit request =>
val partnerID = {
if ((request.body \ "partner_id").as[String].isEmpty) "jobapp"
else (request.body \ "partner_id").as[String]
}
val partnerAuth = {
if ((request.body \ "partner_auth").as[String].isEmpty) "(snip)"
else (request.body \ "partner_auth").as[String]
}
val handshakeURL = {
if ((request.body \ "handshake_url").as[String].isEmpty) "(snip)"
else (request.body \ "handshake_url").as[String]
}
...
wotcService.createWOTC(clientID, (request.body \ "company").as[String], (request.body \ "auth").as[String],
partnerID, partnerAuth, handshakeURL, eligibilityURL, hireURL, pdfURL)
NoContent
}
val partnerAuth = (request.body \ "partner_auth").asOpt[String].getOrElse("SxtWJbboccljiaii")

Play/Scala how to prevent Json serialization of empty arrays?

I want to recursively write a class to Json, so I'm using the following implicit writes:
implicit val writesObject : Writes[Object] = (
(__ \ "id").writeNullable[String] ~
(__ \ "list").lazyWriteNullable(Writes.traversableWrites[Object](writesObject))
)(unlift(Object.unapply)
where Object is a class like this:
case class Object(id: Option[String], list: Option[Seq[Object]])
It works, however I would like to prevent it from printing anything if "list" is empty. For example:
I want:
{ id: "someID",
list: [
{
id: "someOtherId"
}
]
}
I currently get(but don't want):
{ id: "someID",
list: [
{
id: "someOtherId"
list: []
}
]
}
How can I achieve this? I'm new to Play/Scala and not sure exactly what should I be looking at so any pointers would be helpful. I'm using Scala 2.2.1.
PS: I've checked Scala Json Combinators but didn't see any reference on how to get this done.
Update:
So my issue is not that list is null, but that list is empty. That's why lazyWriteNullable wasn't working.
Testing johanandren answer I came up with the following extension to JsPath that returns Option[T] and supports the lazy format for recursive writes:
def lazyWriteNullableIterable[T <: Iterable[_]](w: => Writes[T]): OWrites[Option[T]] = OWrites((t: Option[T]) => {
if(t != null) {
t.getOrElse(Seq.empty).size match {
case 0 => Json.obj()
case _ => Writes.nullable[T](path)(w).writes(t)
}
}
else {
Json.obj()
}
})
Thanks
You can create a custom OFormat that will do this. By implicitly decorating JsPath with it you can include it in your json combinator definitions:
implicit class PathAdditions(path: JsPath) {
def readNullableIterable[A <: Iterable[_]](implicit reads: Reads[A]): Reads[A] =
Reads((json: JsValue) => path.applyTillLast(json).fold(
error => error,
result => result.fold(
invalid = (_) => reads.reads(JsArray()),
valid = {
case JsNull => reads.reads(JsArray())
case js => reads.reads(js).repath(path)
})
))
def writeNullableIterable[A <: Iterable[_]](implicit writes: Writes[A]): OWrites[A] =
OWrites[A]{ (a: A) =>
if (a.isEmpty) Json.obj()
else JsPath.createObj(path -> writes.writes(a))
}
/** When writing it ignores the property when the collection is empty,
* when reading undefined and empty jsarray becomes an empty collection */
def formatNullableIterable[A <: Iterable[_]](implicit format: Format[A]): OFormat[A] =
OFormat[A](r = readNullableIterable(format), w = writeNullableIterable(format))
}
This would allow you to create formats/reads/writes using the json combinator syntax like this:
case class Something(as: List[String], v: String)
import somewhere.PathAdditions
val reads: Reads[Something] = (
(__ \ "possiblyMissing").readNullableIterable[List[String]] and
(__ \ "somethingElse").read[String]
)(Something)
val writes: Writes[Something] = (
(__ \ "possiblyMissing").writeNullableIterable[List[String]] and
(__ \ "somethingElse").write[String]
)(unlift(Something.unapply))
val format: Format[Something] = (
(__ \ "possiblyMissing").formatNullableIterable[List[String]] and
(__ \ "somethingElse").format[String]
)(Something, unlift(Something.unapply))

Create implicit json read for List collection which might be missing from input json

I am following play-salat (github.com/leon/play-salat) to create a model for a json input and save to mongodb. How can I create the implicit json read for List collection which might be missing in the input json? The following code gives me the validation error if the 'positions' is missing from input json.
case class LIProfile(
id: ObjectId = new ObjectId,
positions: List[Position] = Nil
)
object LIProfile extends LIProfileDAO with LIProfileJson
trait LIProfileDAO extends ModelCompanion[LIProfile, ObjectId] {
def collection = mongoCollection("liprofiles")
val dao = new SalatDAO[LIProfile, ObjectId](collection) {}
// Indexes
collection.ensureIndex(DBObject("emailAddress" -> 1), "li_profile_email", unique = true)
// Queries
def findOneByEmail(email: String): Option[LIProfile] = dao.findOne(MongoDBObject("emailAddress" -> email))
}
trait LIProfileJson {
implicit val liprofileJsonWrite = new Writes[LIProfile] {
def writes(p: LIProfile): JsValue = {
Json.obj(
"id" -> p.id,
"positions" -> p.positions
)
}
}
implicit val liprofileJsonRead = (
(__ \ 'id).read[ObjectId] ~
(__ \ 'positions).read (
(__ \ 'values).read[List[Position]]
)
)(LIProfile.apply _)
}
Use readNullable in order to retrieve an Option and map that to the contained list or the empty list.
implicit val liprofileJsonRead = (
(__ \ 'id).read[ObjectId] ~
(__ \ 'positions).readNullable (
(__ \ 'values).read[List[Position]]
).map {
case Some(l) => l
case None => Nil
}
)(LIProfile)
or even shorter:
implicit val liprofileJsonRead = (
(__ \ 'id).read[ObjectId] ~
(__ \ 'positions).readNullable (
(__ \ 'values).read[List[Position]]
).map { l => l.getOrElse(Nil) }
)(LIProfile)
I'm not quite sure what imports you really need here, my code compiles using:
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._