Play Framework Json Serialization Failure - json

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]

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

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.

Play 2 Json format, capture Int or String

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