Play 2 Json format, capture Int or String - json

I have a problem, I use an rest webservice than return an json not well formatted, sometimes return a string sometimes an integer in the same field. This is the code of the format:
implicit val ItemFormat: Format[Item] = (
(JsPath \ "a").format[String] and
(JsPath \ "b").format[String] and
(JsPath \ "c").formatNullable[String]
)(Item.apply , unlift(Item.unapply))
If c is empty or not exist or is a string works well, but if the c is an integer I have this error:
ValidationError(List(error.expected.jsstring),WrappedArray()))
I would obtain, if c is an integer, or convert it in a string or put c=None

You can do it this way.
case class Item(a: String, b: String, c: Option[String])
implicit val reads: Reads[A] = new Reads[A] {
override def reads(json: JsValue): JsResult[A] = {
for {
a <- (json \ "a").validate[String]
b <- (json \ "b").validate[String]
} yield {
val cValue = (json \ "c")
val cOptString = cValue.asOpt[String].orElse(cValue.asOpt[Int].map(_.toString))
Item(a, b, cOptString)
}
}
}

Related

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

Applying conversion to play framework json element before applying to class

I have a class as so
case class Token(
creationDate: Date,
expires: Option[Date]
) {
def toJson(): String = Json.stringify(Json.toJson(this))
}
object Token {
def fromJson(json: String): Token = Json.parse(json).as[Token]
implicit val tokenReads: Reads[Token] = (
(JsPath \ "creation_date").read[Date] and
(JsPath \ "expires").readNullable[Date]
) (Token.apply _)
implicit val tokenWrites: Writes[Token] = (
(JsPath \ "creation_date").write[Date] and
(JsPath \ "expires").writeNullable[Date]
) (unlift(Token.unapply))
}
Which gets created from json like
{
"creation_date": "2014-05-22T08:05:57.556385+00:00",
"expires": null
}
Problem is that date format cant be converted to a date directly, I am looking to somehow take that string, and convert it using
DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String string2 = "2001-07-04T12:08:56.235-07:00";
Date result2 = df2.parse(string2);
and then pass it into the Token constructor, but I cant seem to figure out how to do that in the apply function
You can map String to Date if you have special date format
def strToDate(string2: String): Date = {
//... something such
val df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
df2.parse(string2);
}
implicit val tokenReads: Reads[Token] = (
(JsPath \ "creation_date").read[String].map(strToDate) and
(JsPath \ "expires").readNullable[String].map(_.map(strToDate))
) (Token.apply _)

Play Framework Json Serialization Failure

I have an implicit Json reads and writes as below:
implicit val userJsonWrites = new Writes[User] {
override def writes(user: User): JsValue = Json.obj(
idKey -> Json.toJson(user._id),
firstNameKey -> Json.toJson(user.firstName),
lastNameKey -> Json.toJson(user.lastName),
emailKey -> Json.toJson(user.email),
//passKey -> Json.toJson(user.pass),
addressKey -> Json.toJson(user.address),
createDateKey -> Json.toJson(user.createDate),
activateDateKey -> Json.toJson(user.activateDate),
isUserActivatedKey -> Json.toJson(user.isUserActivated),
verificationDateKey -> Json.toJson(user.verificationDate)
)
}
implicit val userJsonReads = new Reads[User] {
override def reads(json: JsValue): JsResult[User] = {
val user = User(
_id = (json \ idKey).as[Option[String]],
firstName = (json \ firstNameKey).as[String],
lastName = (json \ lastNameKey).as[String],
email = (json \ emailKey).as[String],
pass = (json \ passKey).as[String],
address = (json \ addressKey).as[Address],
createDate = (json \ createDateKey).as[DateTime],
activateDate = (json \ activateDateKey).as[Option[DateTime]],
verificationDate = (json \ verificationDateKey).as[Option[DateTime]],
isUserActivated = (json \ isUserActivatedKey).as[Boolean]
)
JsSuccess(user)
}
}
When I compile, I get the following failures:
Error:(84, 32) not enough arguments for method as: (implicit fjs: play.api.libs.json.Reads[Option[String]])Option[String].
Unspecified value parameter fjs.
_id = (json \ idKey).as[Option[String]],
^
Error:(84, 32) No Json deserializer found for type Option[String]. Try to implement an implicit Reads or Format for this type.
_id = (json \ idKey).as[Option[String]],
^
My User object looks like this:
case class User(
_id: Option[String],
createDate: DateTime,
activateDate: Option[DateTime],
verificationDate: Option[DateTime],
email: String,
pass: String,
firstName: String,
lastName: String,
isUserActivated: Boolean,
address: Address
)
The _id is actually the MongoDB's ObjectId which I had to have as an Option!
Use asOpt, see documentation
It should look like;
...
_id = (json \ idKey).asOpt[String]
You should almost always prefer the provided macro implementations of Reads/Writes/Format over writing your own logic:
import play.api.libs.json._
implicit val userJsonWrites = Json.writes[User]
implicit val userJsonReads = Json.reads[User]