Scala Graph JSON with Play Framework - json

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.

Related

Return a Scala value as a Json string

In Play Framework, I have the following method in the controller:
def country(countryCode: String) =
Authorized().async { implicit request =>
val country = Country.find(countryCode).get
val countryPostcodeZones = postcodeZones.get(country)
val placeholder = countryPostcodeZones.postcodeType.placeholder
}
How can I return this as a Json string to see it on my localhost, in response to an Ajax request?
Not sure what the types of these parameters are, but if they are String, according to the official documentation:
case class ResultCaseClass(country: String, countryPostcodeZones: String, placeholder: String)
implicit val resultWrites: Writes[ResultCaseClass] =
((JsPath \ “country").write[String] and
(JsPath \ "countryPostcodeZones").write[String] and
(JsPath \ “placeholder").write[String] ) (unlift(ResultCaseClass.unapply))
def country(countryCode: String) = Authorized().async { implicit request =>
val country = Country.find(countryCode).get
val countryPostcodeZones = postcodeZones.get(country)
val placeholder = countryPostcodeZones.postcodeType.placeholder
val result = ResultCaseClass(country, countryPostcodeZones, placeholder)
val json = Json.toJson(result)
Ok(json)
}
Note that for simple case classes like this you can also use a macro to generate the Writes automatically:
implicit val resultWrites: Writes[ResultCaseClass] = Json.writes[ResultCaseClass]

Scala - How to implement implicit JSON reader

I've implemented implicit Json Reads in order to read two fields from JSON, saleId and saleType. I wanted to make getSales method return a tuple (Int, String) representing saleId and saleType accordingly. But when I call the getSales method I'm getting the following errors:
Error:(46, 79) No JSON deserializer found for type (Int, String). Try to implement an implicit Reader or JsonFormat for this type.
val salesData = (salesJson \ "sales").as[(Int, String)]
Error:(46, 79) not enough arguments for method as: (implicit reader: org.json4s.Reader[(Int, String)], implicit mf: Manifest[(Int, String)])(Int, String).
Unspecified value parameters reader, mf.
val salesData = (salesJson \ "sales").as[(Int, String)]
I have implemented implicit Json Reads so really confused with the first error. Here is my implementation:
def getsales(context: SparkContext, saleId: Int): (Int, String)= {
val url= buildUrl(context, saleId)
implicit val salesReader: Reads[(Int, String)] = (
(__ \ "id_from_API").read[Int] and
(__ \ "sale_type").read[String]
).tupled
val salesJson: JValue = parse(httpStringResponse(url, context))
val salesData = (salesJson \ "sales_stats").as[(Int, String)]
salesData
}
Two notes concerning you code:
val salesData = (salesJson \ "sales").as[(Int, String)]
val salesData = (salesJson \ "sales_stats").as[(Int, String)]
might have to be the same.
Instead of JValue you might have wanted to put JsValue in the line
val salesJson: JValue = parse(httpStringResponse(url, context))
Other than that testing your JSON reader code separately from the rest of your code might be helpful.
The following worked for me:
import org.scalatest.WordSpec
import play.api.libs.functional.syntax._
import play.api.libs.json._
class ReadsExample extends WordSpec {
"read" in {
val sales =
"""
{
"sales_stats": {
"id_from_API": 42,
"sale_type": "cheap"
}
}
""".stripMargin
implicit val salesReader: Reads[(Int, String)] = (
(JsPath \ "id_from_API").read[Int] and
(JsPath \ "sale_type").read[String]
).tupled
val salesJson: JsValue = Json.parse(sales)
val salesData = (salesJson \ "sales_stats").as[(Int, String)]
}
}
Please note that the version of play-json used here is 2.3.10.
EDIT
code example to the question in the comment
import org.scalatest.WordSpec
import play.api.libs.json.Json.reads
import play.api.libs.json.{Json, _}
class ReadsExample extends WordSpec {
"read" in {
val sales =
"""
{
"id_from_API": 9,
"sale_type": {
"main" : "a",
"sub" : "b"
}
}
""".stripMargin
val salesJson: JsValue = Json.parse(sales)
val salesData = salesJson.as[Sales]
}
}
case class Sales(id_from_API: Int, sale_type: SaleType)
case class SaleType(main: String, sub: String)
object Sales {
implicit val st: Reads[SaleType] = reads[SaleType]
implicit val of: Reads[Sales] = reads[Sales]
}

Scala Play - Iterate over JsArray/JsObject to retrive key and count number of occurance of a key whose value is null

I am using play JSON library to validate a JSON schema.
I have the following JSON data. And I need to validate a schema to this JSON.
// Scala Code
package com.base
import scala.io.Source
import play.api.libs.json._
import play.api.libs.json.JsNull
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import com.utils.ReadJsonFile._
case class JsValueToModel(web_pages: Array[String], name: String, alpha_two_code: String , state_province: Option[String], domains: Array[String], country: String)
object ValidateJSON extends App {
// Parsing Json file to JsArray
val json = Json.parse(readJson1)
// Using Recursive path by each key to traverse json
val web_pages = json \\ "web_pages"
val name = json \\ "name"
val alpha_two_code = json \\ "alpha_two_code"
val state_province = json \\ "state-province"
val domains = json \\ "domains"
val country = json \\ "country"
// Converting JsValue to Model to read the json key value
implicit val JsValueToModelReads: Reads[JsValueToModel] = (
(JsPath \ "web_pages").read[Array[String]] and
(JsPath \ "name").read[String] and
(JsPath \ "alpha_two_code").read[String] and
(JsPath \ "state-province").readNullable[String] and
(JsPath \ "domains").read[Array[String]] and
(JsPath \ "country").read[String]
)(JsValueToModel.apply _)
// Validation Json Object with JsonSchemaModel
val validateJson = json.validate[List[JsValueToModel]] match {
case s: JsSuccess[List[JsValueToModel]] => {
// val v: List[JsValueToModel] = s.get
println("Validation Success")
}
case e: JsError => {
println("Validation Errors: " + JsError.toJson(e).toString)
}
}
// Length of json
println(json.as[JsArray].value.size)
I am getting following error -
json.as[JsObject].map((a,b) => (a == null))
value map is not a member of play.api.libs.json.JsObject
[error] json.as[JsObject].map((a,b) => (a == null))
json.map((a,b) => (a == null))
value map is not a member of play.api.libs.json.JsValue
[error] json.map((a,b) => (a == null))
I am not sure what to change. I tried to do some pattern matching but not able to do - something like -
def findAndCountNull(a: JsValue):(String,Option[Any]) = a match {
case (x,y) => (x == null) // ???
case _ => a
}
Thanks to getting some help regarding this.
I saw some other libraries like json4s, JsZipper as well as from lift but want to use play json library to understand.
Use JSON automated mapping:
import play.api.libs.json.{JsError, JsSuccess, Json}
import scala.io.{Codec, Source}
object Data {
implicit val jsonFormat = Json.format[Data]
}
case class Data(web_pages: Seq[String], name: String, alpha_two_code: String, `state-province`: Option[String], domains: Seq[String], country: String)
val str = Source.fromURL("https://raw.githubusercontent.com/Hipo/university-domains-list/master/world_universities_and_domains.json")(Codec.UTF8).mkString
Json.parse(str).validate[Seq[Data]] match {
case JsSuccess(x, _) =>
Right(x)
case JsError(errors) =>
Left(errors)
}
Result:
res0: scala.util.Either[Seq[(play.api.libs.json.JsPath, Seq[play.api.libs.json.JsonValidationError])],Seq[Data]] =
Right(List(Data(List(https://www.cstj.qc.ca, https://ccmt.cstj.qc.ca, https://ccml.cstj.qc.ca),Cégep de Saint-Jérôme,CA,None,List(cstj.qc.ca),Canada), Data(List(http://www.lindenwood.edu/),Lindenwood University,US,None,List(lindenwood.edu),United States), Data(List(http://www.davietjal.org/),DAV Institute of Engineering & Technology,IN,Some(Punjab),List(davietjal.org),India), Data(List(http://www.lpu.in/),Lovely Professional University,IN,Some(Punjab),List(lpu.in),India), Data(List(https://sullivan.edu/),Sullivan University,US,None,List(sullivan.edu),United States), Data(List(https://www.fscj.edu/),Florida State College at Jacksonville,US,None,List...
For just counting null - use pattern matching (simplified - you should check JSArray too):
val parsed = Json.parse(str)
val seq = parsed.as[JsArray].value.map(_ \\ "state-province").map(x => x.head)
val nulls = seq.count {
case JsNull => true
case _ => false
}
println(s"$nulls")

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.

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