read Json into Scalaz Tree - scala

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

Related

How to write a play-json Reads for Map[(Int, Int), A]?

I want to deserialize this json, which represents a rack that is a 5x5 playing field and contains one tile at position (1,2):
{
"rows" : 5,
"cols" : 5,
"tiles" : [ {
"row" : 1,
"col" : 2,
"tile" : {
"number" : 3
}
} ]
}
So my case classes are:
case class Tile(number: Int)
case class Rack(rows: Int, cols: Int, tiles: Map[(Int, Int), Tile])
I tried to write a Reads for the class rack:
implicit val tileWrites = Json.writes[Tile]
implicit val tileReads = Json.reads[Tile]
implicit val reads: Reads[Rack] = (
(__ \ "rows").read[Int] and
(__ \ "cols").read[Int] and
(__ \ "tiles").read[Map[(Int, Int), Tile]]
) (Rack.apply _)
But i got this error:
Error:(50, 26) No Json deserializer found for type Map[(Int, Int),de.htwg.se.rummi.model.model.Tile]. Try to implement an implicit Reads or Format for this type.
(__ \ "tiles").read[Map[(Int, Int), Tile]]
Can someone explain how I write a Reads for Map[(Int, Int), Tile]?
I wouldn't recommend defining a custom Reads instance for common types like Map. Instead I would recommend defining a custom type for the tiles and implement a Reads for that. The advantage is that you can place the Reads instance in the companion object and the compiler will always find the correct instance without you having to import anything.
case class Tiles(tiles: Map[(Int, Int), Tile])
Now you can define a Reads instance for Tiles in the companion object:
import play.api.libs.json._
import play.api.libs.functional.syntax._
object Tiles {
implicit val reads: Reads[Tiles] =
Reads.list {
(
(__ \ "row").read[Int] and
(__ \ "col").read[Int] and
(__ \ "tile").read[Tile]
) { (row, col, tile) => ((row, col), tile) }
}.map(tuples => Tiles(tuples.toMap))
}

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

unable to create an implicit read for JSON

My server code will receive the following JSON
{
"signin-info":{
"email" : "someemail",
"password": "password"
}
}
I have created the following 2 case classes to represent this structure.
case class UserSigninInfo(
email:String,
password:String
)
case class UserSignin(
signinInfo: UserSigninInfo
)
To read the JSON, I have created the following 2 Reads
implicit val userSigninInfoReads:Reads[UserSigninInfo] = (
(JsPath \ "email").read[String] and
(JsPath \ "password").read[String]
)(UserSigninInfo.apply _)
implicit val userSigninReads:Reads[UserSignin] = (
(JsPath \ "signin-info").read[UserSigninInfo]
)(UserSignin.apply _)
But for userSigninReads, I am getting the following compilation error. What am I doing wrong?
overloaded method value read with alternatives:
[error] (t: models.UserSigninInfo)play.api.libs.json.Reads[models.UserSigninInfo] <and>
[error] (implicit r: play.api.libs.json.Reads[models.UserSigninInfo])play.api.libs.json.Reads[models.UserSigninInfo]
[error] cannot be applied to (models.UserSigninInfo => models.UserSignin)
[error] (JsPath \ "signin-info").read[UserSigninInfo]
[error] `
The solution to construct reads for your main object is:
implicit val userSigninReads : Reads[UserSignin] =
(JsPath \ "signin-info").read[UserSigninInfo].map(UserSignin(_))
This is because it only has a single field.
Your construction was essentailly:
((JsPath \ "signin-info").read[UserSigninInfo])(UserSignin.apply _)
which is simply
(JsPath \ "signin-info").read[UserSigninInfo](UserSignin.apply _)
because it just adds extra ().
read method indeed has two alternatives:
def read[T](t: T) = Reads.pure(t)
def read[T](implicit r: Reads[T]): Reads[T] = Reads.at[T](this)(r)
It expects either implicit reads or explicit value, but you are passing a function, this is why compiler complains.
When you combine multiple fields with and, different object is created, e.g. FunctionalBuilder[M]#CanBuild2[A, B] for 2 elements, and it has apply method to construct final Reads instance.
As you only have one object you can create your Reads by reading UserSigninInfo from the path you want and putting the result into UserSignin with map.

Extract fields from JsArray in scala on play framework

I have a JSArray like below from server (I cannot change as it belongs to others):
[ {"name": "US", "id": 0, "translations" : {"name: {"es": "Estados unidos", "fr": "Etats-unis"}}},
{"name": "UK", "id": 1, "translations" : {"name: {"es": "Estados Kdda", "fr": "dsfjas"}}},
...
]
I need to extract all the name like US, UK but not name in translations and also id.
I tried several ways and it always have problem. Below are what I tried.
I first tried
case class Country(name: String, id:String)
implicit object CountryReads extends Reads[Country] {
def reads(json: JsValue) = Country(
(json \ "name"),
(json \ "id")
)
}
val countries = Json.parse(result) match { //here result is Json String
case JsArray(Seq(t)) => Some(t.as[Seq[Country]])
case _ => None
}
But I get compiling error as below:
[error] C:\git9\hss\app\models\LivingSocial.scala:80: type mismatch;
[error] found : play.api.libs.json.JsValue
[error] required: play.api.libs.json.JsResult[MyCountry]
[error] def reads(json: JsValue) = (json \ "currencyCode")
[error] ^
[error] one error found
[error] (compile:compile) Compilation failed
then I tried:
val jResult = Json.parse(result)
(jResult \\ "name").foreach { name =>
println(name.as[String])
}
I get error in println() as "\" will recursively pull names under translation also.
Any good way to do it?
case class Country(name: String, id: Int, translations: Map[String, Map[String, String]])
object Country {
implicit val format = Json.format[Country]
}
val js = Json.parse(yourString)
val names: JsResult[Seq[String]] = js.validate[Seq[Country]].map { countries => countries.map(_.name) }
At that point you can deal with the JsResult since you'll need error handling in case the JSON doesn't conform to your expectations.
You can change your read to look like this :
implicit val CountryReads: Reads[Country] = (
(JsPath \ "name").read[String] and
(JsPath \ "name").read[String]
)(Country.apply _)
this is a correct way to create a reader according to play documentation

No Json formatter found Scala, Play framework error

I have following two implicits.
implicit val readObjectIdFormat = new Reads[ObjectId] {
def reads(jv: JsValue): JsResult[ObjectId] = {
JsSuccess(new ObjectId(jv.as[String]))
}
}
implicit val visitorFormat = (
(__ \ "_id").formatOpt[ObjectId] and
(__ \ "visitorId").format[String] and
(__ \ "referralUrl").formatOpt[String] and
(__ \ "ipAddress").formatOpt[String] and
(__ \ "promotionId").format[String])(Visitor)
Though readObjectIdFormat is defined at compile time it keeps complaining following on "(__ \ "_id").formatOpt[ObjectId]" line
No Json formatter found for type org.bson.types.ObjectId. Try to implement an implicit
Format for this type.
versions : Play 2.1-RC2, Scala 2.10
Any idea why it's not recognizing readObjectIdFormat ?
Others gave the good answer, use Format instead.
By the way, you could handle parse errors.
This implementation is working fine for me:
implicit val objectIdFormat: Format[ObjectId] = new Format[ObjectId] {
def reads(json: JsValue) = {
json match {
case jsString: JsString => {
if ( ObjectId.isValid(jsString.value) ) JsSuccess(new ObjectId(jsString.value))
else JsError("Invalid ObjectId")
}
case other => JsError("Can't parse json path as an ObjectId. Json content = " + other.toString())
}
}
def writes(oId: ObjectId): JsValue = {
JsString(oId.toString)
}
}
You are implementing Reads and you need implement Format instead.
implicit val readObjectIdFormat = new Format[ObjectId] {
def reads(jv: JsValue): JsResult[ObjectId] = {
JsSuccess(new ObjectId(jv.as[String]))
}
def writes(o: A): JsValue = JsString(...)
}
Or you need to use the read instead of format (note I assume this works for read, haven't tested it).
implicit val visitorRead = (
(__ \ "_id").readOpt[ObjectId] and
(__ \ "visitorId").read[String] and
(__ \ "referralUrl").readOpt[String] and
(__ \ "ipAddress").readOpt[String] and
(__ \ "promotionId").read[String])(Visitor)
From documentation: Format[T] extends Reads[T] with Writes[T]
Format is a read + write.
Write an implicit writeObjectIdFormat then
implicit val formatObjectIdFormat =
Format(readObjectIdFormat, writeObjectIdFormat)