How to parse json with a single field in Playframework2? - scala

I am trying to parse a Travis-ci api response which has the following structure :
{
repos: [
{"id": ..., "slug": ...},
{"id": ..., "slug": ...},
{"id": ..., "slug": ...}
]
}
So I have decided to create case classes reflecting the json structure :
case class TravisRepository(id: String, slug: String)
case class TravisUserRepositories(repos: Seq[TravisRepository])
And I have added the implicit Read methods :
implicit val travisRepositoryReads: Reads[TravisRepository] = (
(JsPath \ "id").read[String] and
(JsPath \ "slug").read[String]
)(TravisRepository.apply _)
implicit val travisUserRepositoriesReads: Reads[TravisUserRepositories] = (
(JsPath \ "repos").read[Seq[TravisReposity]]
)(TravisUserRepositories.apply _)
However the second read is not compiling with the following error :
Overloaded method value [read] cannot be applied to (Seq[utils.TravisRepository] => utils.TravisUserRepositories)
When adding another column to the second Read, this compiles. With a single column, this is not compiling anymore. Can someone explain why is this not compiling? Is it a limitation of the Play-Json parser?

That's simply because you have the case "only one single field in your case class"...
To be able to use the Functional combining, you need at least 2 fields.
// would be OK implicit val travisUserRepositoriesReads:
Reads[TravisUserRepositories] = (
(JsPath \ "repos").read[Seq[TravisReposity]] and
... )(TravisUserRepositories.apply _)
// should be OK implicit val travisUserRepositoriesReads:
Reads[TravisUserRepositories] = (
(JsPath \ "repos").read[Seq[TravisReposity]] map (TravisUserRepositories.apply _)

Related

Transform Value In PlayJSon Mapping

I'm performing a standard mapping of JSON to a case class using PlayJson. I'd like to transform the value that gets mapped to a member, Test.foo below, if the validation succeeds. Is it possible to work that into the definition of a Reads converter?
val json = .....
case class Test(foo:String, bar:String)
val readsTest: Reads[Test] = (
(__ \ "foo").read[String](minLength(5)) and // And I want to transform this value if the validation succeeds
(__ \ "bar").read[String](minLength(10))
)(Test.apply _)
json.validate[Test] match {
case s: JsSuccess[Test] => s.get
case e: JsError => false
}
Reads.map can do just that, for example, say we want to reverse the value of foo field, then we could call .map(v => v.reverse) on the Reads like so
(__ \ "foo").read[String](minLength[String](5)).map(v => v.reverse)
Here is a working example
val json =
"""
|{
| "foo": "abcdefghijkl",
| "bar": "012345678910"
|}
|""".stripMargin
case class Test(foo: String, bar: String)
val readsTest: Reads[Test] = (
(__ \ "foo").read[String](minLength[String](5)).map(v => v.reverse)
(__ \ "bar").read[String](minLength[String](10))
)(Test.apply _)
Json.parse(json).validate[Test](readsTest)
which outputs
JsSuccess(Test(lkjihgfedcba,012345678910),)

Validation Only With Play Json

I'd like to use PlayJson to only validate multiple fields of some json and not map it to a custom object. I Only care about the Yes Or No answer to the validation criteria. Is it possible to use PlayJson in that way? So far I have something like,
val json = .....
val reads = (JsPath \ "foo").read[String](min(5)) and
(JsPath \ "bar").read[String](max(10))
json.validate["I ONLY WANT TO VALIDATE NOT MAP"](reads) match {
case s: JsSuccess => true
case e: JsError => false
}
Thank you Stack Overflow community.
Instead of deserialising to a case class model via Reads[MyModel] we can deserialise to a tuple via Reads[(String, String)] like so
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
val reads = (
(JsPath \ "foo").read[String](minLength[String](5)) and
(JsPath \ "bar").read[String](minLength[String](10))
).tupled
val json = Json.parse(
"""
|{
| "foo": "abcde",
| "bar": "woohoowoohoo",
| "zar": 42
|}
|""".stripMargin)
json.validate(reads).isSuccess
which outputs
res0: Boolean = true
Note how we called tupled method when creating the reader, and isSuccess to get a boolean out of validation process.
https://scalafiddle.io/sf/JBjdt2Y/0

Scala play framework: complex read to match multiple keys for same field in case class

i'm a scala newbie...
let's say i have a case class like this:
case class Event(name: Option[String]) {}
i want to use the Play framework to parse it. however, sometimes i get a json payload where the first letter of the key is uppercase and sometimes lowercase. like so:
lowercase
{
"name": "group_unsubscribe",
}
uppercase
{
"Name": "group_unsubscribe",
}
how can i account for these possibilities using a complex reads?
i have tried with things like:
implicit val reads: Reads[Event] = (
((JsPath \ "name").readNullable[String] or
(JsPath \ "Name").readNullable[String])
)(Event.apply _)
but no joy :(
You need to re-write your Reads as:
implicit val reads: Reads[Event] = (
(JsPath \ "name").readNullable[String] orElse
(JsPath \ "Name").readNullable[String]
).map(Event(_))
Update 1 taking into account the comments:
import play.api.libs.json.Reads
implicit val reads: Reads[Event] = (
(JsPath \ "name").read[String] orElse
(JsPath \ "Name").read[String]
).map(name => Event(Option(name)))
Note: this implementation assumes that either "name" or "Name" will always be present in the incoming JSON document.
In order to capture the possibility of failure, you should use .validate[T] instead of .as[T].
Update 2 taking into account further comments:
Whether you have one or more attributes in your type doesn't change much. If your type had another field called somethingElse you would need to adapt your Reads to something like:
implicit val reads: Reads[Event] = (
((JsPath \ "name").read[String] orElse
(JsPath \ "Name").read[String]).map(Option(_)) ~
(JsPath \ "somethingElse").read[String]
)(Event.apply _)

read Json into Scalaz Tree

Scala newbie here.
I use Play to provide a json API for reading and writing a directory like structure. Therefore I use Scalaz.Tree, which provides ways of traversing, updating and rebuilding the Tree.
Formatting the Tree into json works well:
case class File(id: String = BSONObjectID.generate.toString(), name: String, noteBookId: String = null)
implicit val fileFormat: Format[File] = Json.format[File]
implicit def treeWrites: Writes[Tree[File]] =
new Writes[Tree[File]] {
def writes(o: Tree[File]) = o match {
case Node(file, children) => Json.obj(
"name" -> file.name,
"id" -> file.id,
"children" -> JsArray(children.map(Json.toJson(_))),
"notebookId" -> file.noteBookId
)
}
}
Reading json into a Tree however, fails
implicit def treeReads: Reads[Tree[File]] = (
//(__ \ "children").lazyRead(Reads.seq[File](treeReads)) and
(__ \ "children").read[Tree[File]] and
(__ \ "name").read[String] and
(__ \ "notebookid").read[String] and // <-- this is line 41, where the error message points at!!
(__ \ "id").read[String]
)(apply _)
implicit val treeFormat: Format[Tree[File]] = Format(treeReads, treeWrites)
The error I am getting:
[error] /home/dikken/Development/core-service-spaas/app/models/dirTree.scala:41: overloaded method value apply with alternatives:
[error] [B](f: B => (scalaz.Tree[model.treedir.File], String, String, String))(implicit fu: play.api.libs.functional.ContravariantFunctor[play.api.libs.json.Reads])play.api.libs.json.Reads[B] <and>
[error] [B](f: (scalaz.Tree[model.treedir.File], String, String, String) => B)(implicit fu: play.api.libs.functional.Functor[play.api.libs.json.Reads])play.api.libs.json.Reads[B]
[error] cannot be applied to ((=> Nothing) => scalaz.Tree[Nothing])
[error] (__ \ "id").read[String] and
[error] ^
[error] one error found
[error] (compile:compile) Compilation failed
Does this mean I have to pattern match on a case where I have a Tree of Nothing? And how excatly should I do that?
Any help appreciated! Tx!
I'm going to assume that apply _ is actually File.apply _, which cannot work here. File.apply accepts the parameters of the case class File (of which there are three). With JSON combinators, it is trying to pass the four parameters above to File.apply, which does not mix. It also does not produce a Tree[File]. What you need to do is replace File.apply with a method that accepts (children, notebookid, name, id) as parameters, and produces a Tree[File].
Here's a somewhat crude one that will work:
def jsonToTree(children: Seq[Tree[File]], name: String, notebookid: String, id: String): Tree[File] =
Tree.node(File(id, name, notebookid), children.toStream)
The Reads will now look more like this:
implicit def treeReads: Reads[Tree[File]] = (
(__ \ "children").lazyRead[Seq[Tree[File]]](Reads.seq(treeReads)).orElse(Reads.pure(Nil)) and
(__ \ "name").read[String] and
(__ \ "notebookid").read[String] and
(__ \ "id").read[String]
)(jsonToTree _)
You were closer with the commented out line as well. Because this is a recursive structure, we need to use lazyRead.
Testing:
val js = Json.parse("""{
"id": "1",
"name": "test",
"notebookid": "abc",
"children": [
{
"id": "2",
"name": "test222",
"notebookid": "ijk"
},
{
"id": "3",
"name": "test333",
"notebookid": "xyz"
}
]
}""")
scala> val tree = js.as[Tree[File]]
tree: scalaz.Tree[File] = <tree>
scala> tree.rootLabel
res8: File = File(1,test,abc)
scala> tree.subForest
res9: Stream[scalaz.Tree[File]] = Stream(<tree>, ?)
This can also be done (certainly in different ways) without combinators, as well (provided there is an implicit Reads[File] available):
implicit def treeReads: Reads[Tree[File]] = new Reads[Tree[File]] {
def reads(js: JsValue): JsResult[Tree[File]] = {
js.validate[File] map { case file =>
(js \ "children").validate[Stream[Tree[File]]].fold(
_ => Tree.leaf(file),
children => Tree.node(file, children)
)
}
}
}

Why should I choose combinators over class construction for JSON serialisation?

The Play Scala documentation shows how to serialise JSON in two ways, first:
implicit val locationWrites = new Writes[Location] {
def writes(location: Location) = Json.obj(
"lat" -> location.lat,
"long" -> location.long
)
}
Then with combinators:
implicit val locationWrites: Writes[Location] = (
(JsPath \ "lat").write[Double] and
(JsPath \ "long").write[Double]
)(unlift(Location.unapply))
What reasons should be considered to choose one approach over the other?