Extract fields from JsArray in scala on play framework - scala

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

Related

What is the best way to handle String values that are also null in my JSON file?

I am currently getting decoding failures for String types where the JSON file has nulls.
case class User(
updatedAt: String,
name: String
)
Json looks like:
{
"updated_at": null,
"name": "John"
}
In my tests are have:
"parse User from json file" in {
val jsonString = Source.fromURL(getClass.getResource("/user.json")).mkString
val expected = User(
null,
"John"
)
parser.decode[User](jsonString) must_=== Right(expected)
}
I have the following implicits:
implicit val customConfig: Configuration = Configuration.default.withSnakeCaseMemberNames
implicit val userDe: Decoder[User] = deriveConfiguredDecoder[User]
implicit val userEn: Encoder[User] = deriveConfiguredEncoder[User]
When I run my tests, I get this test failure:
[error] Actual: Left(DecodingFailure(String, List(DownField(updated_at))))
[error] Expected: Right(User(null,John))
What is wrong with my setup here?

value of case class parameter is not a member of Serializable

I am trying to build a validator for Json structure, and at this point I already have something as follows:
Reads definitions
case class SubTaskConfigElement(name: String)
case class MultiSelectConfig(subTasks: Seq[SubTaskConfigElement])
implicit val subTaskConfigElementReads: Reads[SubTaskConfigElement] =
(__ \ "name").read[String](minLength[String](0)).map(SubTaskConfigElement)
implicit val multiSelectConfigReads: Reads[MultiSelectConfig] = (
(__ \ "subTasks").read[Seq[SubTaskConfigElement]]
).map(MultiSelectConfig)
And I have a unit test as follows:
val configJson = Json.parse(
"""
|{
| "subTasks": [
| { "name": "Sub Task 1" },
| { "name": "Sub Task 2" },
| { "name": "Sub Task 3" }
| ]
|}
""".stripMargin)
val valid = configJson.validate[MultiSelectConfig] getOrElse JsError
logger.info(valid + "")
valid must beAnInstanceOf[MultiSelectConfig]
valid.subTasks must beAnInstanceOf[List[SubTaskConfigElement]]
In the last line of the test I am getting an error when I execute the test:
[error] /app/process-street/test/validation/widget/config/FormFieldWidgetSpec.scala:29: value subTasks is not a member of Serializable
[error] valid.subTasks must beAnInstanceOf[Seq[SubTaskConfigElement]]
IntelliJ also identifies it as a problem with: "Cannot resolve symbol subTasks"
Why is it happening? What so I miss?
Thanks.
The issue is this line:
val valid = configJson.validate[MultiSelectConfig] getOrElse JsError
The type of valid is inferred as Serializable, because that's the common parent type of MultiSelectConfig and JsError. Both are case classes and case classes automatically inherit from `Serializable.

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

scala trouble with play framework JSON serialization from case class

Play 2.2.1, scala 2.10
// PersonModel.scala
case class PersonModel(name: String, age: Long)
object PersonModel2 {
implicit object PersonModelFormat extends Format[PersonModel] {
def reads(json: JsValue): PersonModel = PersonModel(
(json \ "name").as[String],
(json \ "age").as[Long])
def writes(u: PersonModel): JsValue = JsObject(List(
"name" -> JsString(u.name),
"age" -> JsNumber(u.age)))
}
sbt says
[error] PersonModel.scala:15: overriding method reads in trait Reads of type (json: play.api.libs.json.JsValue)play.api.libs.json.JsResult[models.PersonModel];
[error] method reads has incompatible type
[error] def reads(json: JsValue): PersonModel = PersonModel(
[error] ^
In this case, since you're not doing fancy things with the output json (like changing the key names in the resulting json object), I'd go for:
case class PersonModel(name: String, age: Long)
import play.api.libs.json._
implicit val personModelFormat = Json.format[PersonModel]
This way, you can, for example
scala> val j = PersonModel("julien", 35)
j: PersonModel = PersonModel(julien,35)
scala> println(Json.toJson(j))
{"name":"julien","age":35}
More info can be found here
HTH,
Julien
Things have changed in recent versions, I'd say for the better. In 2.2.x, you'd do it this way, using the new functional syntax and combinators:
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val PersonModelFormat: Format[PersonModel] = (
(__ \ "name").format[String] and
(__ \ "age").format[Long]
)(PersonModel.apply, unlift(PersonModel.unapply))
Much shorter!
The documentation for 2.2.x http://www.playframework.com/documentation/2.2.1/ScalaJsonCombinators provides a good explanation for the rationale for the change.
For single usage there is an inline solution:
Json.writes[PersonModel].writes(personModelInstance) // returns JsObject
From documentation:
macro-compiler replaces Json.writes[User] by injecting into compile chain the exact code you would write yourself

No Json deserializer found for type java.util.Date

I'm working on the following lines of code.
val list = Car.getNames()
Ok(Json.toJson(list))
I got the following errors....
[error] my_app/app/models/Car.scala:51: No Json deserializer found for type java.util.Date. Try to implement an implicit Reads or Format for this type.
Car has java.util.date object as one of parameters and I implemented the Reads and Writes to support the java.util.date object since import play.api.libs.json.* does not support it.
Would you point my mistakes?
implicit object CarFormat extends Format[Car] {
def reads(json: JsValue): Car = Car(
(json \ "id").as[Long],
(json \ "height").as[Double],
(json \ "weight").as[Double],
(json \ "date").asOpt[java.util.Date]
)
def writes(car: Car) =
JsObject(
Seq(
"id" -> JsString(car.id.toString),
"height" -> JsString(car.height.toString),
"weight" -> JsString(car.weight.toString),
"date" -> JsString(car.date.toString)
)
)
}
You only defined Format for Car but it requires a Format for java.util.Date. Try this:
import play.api.libs.json._
case class Car(id:Long, height:Double, weight:Double, date:Option[java.util.Date])
implicit object CarFormat extends Format[Car] {
implicit object DateFormat extends Format[java.util.Date] {
val format = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
def reads(json:JsValue): java.util.Date = format.parse(json.as[String])
def writes(date:java.util.Date) = JsString(format.format(date))
}
def reads(json: JsValue): Car = Car(
(json \ "id").as[Long],
(json \ "height").as[Double],
(json \ "weight").as[Double],
(json \ "date").asOpt[java.util.Date]
)
def writes(car: Car) =
JsObject(
Seq(
"id" -> JsString(car.id.toString),
"height" -> JsString(car.height.toString),
"weight" -> JsString(car.weight.toString),
"date" -> JsString(car.date.toString)
)
)
}