Scala - How to implement implicit JSON reader - json

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

Related

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

Play Json: custom reads one field

Let's say I have to write custom Reads[Person] for Person class:
import play.api.libs.functional.syntax._
implicit val personReads: Reads[Person] = (
(__ \ "name").read[String] and // or ~
(__ \ "age").readNullable[Int]
) ((name, age) => Person(name = name, age = age))
it works like a charm, really (no).
But what can I do when there is only one field in json object?
The core of Reads and Writes is in functional syntax which transforms these "parse" steps.
The following does not compile:
import play.api.libs.functional.syntax._
implicit val personReads: Reads[Person] = (
(__ \ "name").read[String]
)(name => Person(name))
Could you advice how to deal with it?
Option 1: Reads.map
import play.api.libs.json._
case class Person(name: String)
object PlayJson extends App {
implicit val readsPeson: Reads[Person] =
(__ \ "name").read[String].map(name => Person(name))
val rawString = """{"name": "John"}"""
val json = Json.parse(rawString)
val person = json.as[Person]
println(person)
}
Option 2: Json.reads
import play.api.libs.json._
case class Person(name: String)
object Person {
implicit val readsPerson = Json.reads[Person]
}
object PlayJson extends App {
val rawString = """{"name": "John"}"""
val json = Json.parse(rawString)
val person = json.as[Person]
println(person)
}

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

Deserializer for List[T] types

I'm trying to deserialize a JsArray into a List[T] in a playframework application using Scala. After some research I found this method which is supposed to do the needed work:
/**
* Deserializer for List[T] types.
*/
implicit def listReads[T](implicit fmt: Reads[T]): Reads[List[T]] = new Reads[List[T]] {
def reads(json: JsValue) = json match {
case JsArray(ts) => ts.map(t => fromJson(t)(fmt)).toList
case _ => throw new RuntimeException("List expected")
}
}
The problem is that I didn't know how to use it. Any help is welcome.
Here's a quick example:
scala> import play.api.libs.json._
import play.api.libs.json._
scala> Json.toJson(List(1, 2, 3)).as[List[Int]]
res0: List[Int] = List(1, 2, 3)
And if you have a custom type with a Format instance:
case class Foo(i: Int, x: String)
implicit object fooFormat extends Format[Foo] {
def reads(json: JsValue) = Foo(
(json \ "i").as[Int],
(json \ "x").as[String]
)
def writes(foo: Foo) = JsObject(Seq(
"i" -> JsNumber(foo.i),
"x" -> JsString(foo.x)
))
}
It still works:
scala> val foos = Foo(1, "a") :: Foo(2, "bb") :: Nil
foos: List[Foo] = List(Foo(1,a), Foo(2,bb))
scala> val json = Json.toJson(foos)
json: play.api.libs.json.JsValue = [{"i":1,"x":"a"},{"i":2,"x":"bb"}]
scala> json.as[List[Foo]]
res1: List[Foo] = List(Foo(1,a), Foo(2,bb))
This approach would also work if your custom type had a xs: List[String] member, for example: you'd just use (json \ "xs").as[List[String]] in your reads method and Json.toJson(foo.xs) in your writes.