Traversing multiple JSON arrays in Play/Scala - json

{
"location":{
"residents":[{
"renting":[{
"name":"John Doe"
"pets":"2"
},{
"name":"Jane Smith"
"pets":"2"
}]
}]
}
}
I can successfully traverse location with this -
val json = ...
val rentReads = (__ \ 'location).read[String]
val rentResult = json.validate[String](rentReads)
rentResult match {
case s: JsSuccess[String] => Ok(s.get)
case e: JsError => Ok("Errors: " + JsError.toFlatJson(e).toString())
}
Based on the documentation, I should be able to do something like this -
val skillReads = ((__ \ 'location) \ 'residents)(0).read[String]
but it results in the following error -
Errors: {"obj.VariationResultsWrapper.VariationResultSets[0]":[{"msg":"error.path.missing","args":[]}]}
At this point I'm just trying to understand how to return values from "renting" only. Eventually, I would like to map that result to a case class.

If your eventual goal is to parse this into case classes, just define those case classes and let Play do the heavy lifting.
case class Renting(name: String, pets: String)
case class Resident(renting: List[Renting])
case class Location(residents: List[Resident])
implicit val rentingFormat = Json.format[Renting]
implicit val residentFormat = Json.format[Resident]
implicit val locationFormat = Json.format[Location]
(json \ "location").validate[Location]
res1: play.api.libs.json.JsResult[Location] = JsSuccess(Location(List(Resident(List(Renting(John Doe,2), Renting(Jane Smith,2))))),/residents)

Related

Is JSON formatting not enough for parsing case class having > 22 fields?

My case class has 30 fields. For simplicity, I use 4 fields,
case class Person(id: Long, name: String, age: Int, sex: Sex)
val personFormat1: OFormat[(Long, String)] = ((__ \ "id").format[Long] ~ (__ \ "name").format[String]).tupled
val personFormat2: OFormat[(Int, Sex)] = ((__ \ "age").format[Int] ~ (__ \ "sex").format[Sex]).tupled
implicit val personFormat: Format[Person] = (personFormat1 ~ personFormat2)({
case ((id, name), (age, sex)) => new Person(id, name, age, sex)
}, (person: Format) => ((person.id, person.name), (person.age, person.sex)))
But even after writing formatter with format1 as a group of 22 fields and format2 as a group of 8 fields, I get error when am trying to parse the json of this case class.
Error is
No Json serializer as JsObject found for type Person. Try to implement an implicit OWrites or OFormat for this type.
How to write implicit Owrites or OFormat? or how to fix this issue?
I use Play-Json extensions library for working with JSON with more than 22 fields: https://github.com/xdotai/play-json-extensions
libraryDependencies += "ai.x" %% "play-json-extensions" % "0.8.0"
You need to an implicit writer to do this. Something like this
implicit val person= Json.format[Person]
Also, if you are using custom data types, like for your case Sex you need to specify a reader and writer. You do not need to do this for primitive types like Int, Long, String Etc.
def enumReads[T <: Enum[T]](mkEnum: String => T) = new Reads[T] {
override def reads(json: JsValue): JsResult[T] =
json match {
case JsString(s) => try {
JsSuccess(mkEnum(s))
}
catch {
case e: IllegalArgumentException =>
JsError("Not a valid enum value: " + s)
}
case v => JsError("Can't convert to enum: " + v)
}
}
implicit val enumWrites = new Writes[Enum[_]] {
def writes(e: Enum[_]) = JsString(e.toString)
}
implicit val sex = enumReads(Sex.valueOf)
Also, upgrade to scala 2.11 or later to avoid the limitation of 22 fields in case class. For more info see here: How to get around the Scala case class limit of 22 fields?
I found this using google, and this worked greatly for me
// https://mvnrepository.com/artifact/com.chuusai/shapeless_2.11
libraryDependencies += "com.chuusai" % "shapeless_2.11" % "2.3.2"
// https://mvnrepository.com/artifact/org.julienrf/play-json-derived-codecs_2.11
libraryDependencies += "org.julienrf" % "play-json-derived-codecs_2.11" % "3.2"

Parse Jsvalue using scala

I'm new at scala. I have a Json File that I rea it into String. Then I'm parsing the string to a JSValue. Now I'm trying to read all values to update my database, but i don't know how to proceed.
val l = scala.io.Source.fromFile("list.json").getLines().mkString
val result: JsValue = Json.parse(l)
My Json is like :
{
picture_id : xxx
width : xxx
height : xxx
},
{
picture_id : xxx
width : xxx
height : xxx
},
....
I want to extract even block to update database with right values.
Thx.
You should do something like below. I hope the comments are explanatory:
val l = scala.io.Source.fromFile("list.json").getLines().mkString
val result: JsValue = Json.parse(l)
//Create a model to hold your json objects
case class Pic(id: String, width: String, height: String)
//Create a reader that reads your json string to your model(Pic)
implicit val picReads: Reads[Pic] = (
(JsPath \ "picture_id").read[String] and
(JsPath \ "width").read[String] and
(JsPath \ "height").read[String] and
)(Pic.apply _)
result.validate[List[Pic]] match {
case s: JsSuccess[List[Pic]] =>
//Deal with your list of pics here
case e: JsError => println("Errors: " + JsError.toFlatJson(e).toString())
}

Outputting 'null' for Option[T] in play-json serialization when value is None

I'm using play-json's macros to define implicit Writes for serializing JSON. However, it seems like by default play-json omits fields for which Option fields are set to None. Is there a way to change the default so that it outputs null instead? I know this is possible if I define my own Writes definition, but I'm interested in doing it via macros to reduce boilerplate code.
Example
case class Person(name: String, address: Option[String])
implicit val personWrites = Json.writes[Person]
Json.toJson(Person("John Smith", None))
// Outputs: {"name":"John Smith"}
// Instead want to output: {"name":"John Smith", "address": null}
The Json.writes macro generates a writeNullable[T] for optional fields. Like you know (or not), writeNullable[T] omits the field if the value is None, whereas write[Option[T]] generates a null field.
Defining a custom writer is the only option you have to get this behavior.
(
(__ \ 'name).write[String] and
(__ \ 'address).write[Option[String]]
)(unlift(Person.unapply _))
You can use a custom implicit JsonConfiguration, see Customize the macro to output null
implicit val config = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
implicit val personWrites = Json.writes[Person]
Json.toJson(Person("John Smith", None))
Not a real solution for you situation. But slightly better than having to manually write the writes
I created a helper class that can "ensure" fields.
implicit class WritesOps[A](val self: Writes[A]) extends AnyVal {
def ensureField(fieldName: String, path: JsPath = __, value: JsValue = JsNull): Writes[A] = {
val update = path.json.update(
__.read[JsObject].map( o => if(o.keys.contains(fieldName)) o else o ++ Json.obj(fieldName -> value))
)
self.transform(js => js.validate(update) match {
case JsSuccess(v,_) => v
case err: JsError => throw new JsResultException(err.errors)
})
}
def ensureFields(fieldNames: String*)(value: JsValue = JsNull, path: JsPath = __): Writes[A] =
fieldNames.foldLeft(self)((w, fn) => w.ensureField(fn, path, value))
}
so that you can write
Json.writes[Person].ensureFields("address")()
Similar answer to above, but another syntax for this:
implicit val personWrites = new Writes[Person] {
override def writes(p: Person) = Json.obj(
"name" -> p.name,
"address" -> p.address,
)
}
This is simple:
implicit val personWrites = new Writes[Person] {
override def writes(p: Person) = Json.obj(
"name" -> p.name,
"address" -> noneToString(p.address),
)
}
def optToString[T](opt: Option[T]) =
if (opt.isDefined) opt.get.toString else "null"
You can define something like this :
implicit class JsPathExtended(path: JsPath) {
def writeJsonOption[T](implicit w: Writes[T]): OWrites[Option[T]] = OWrites[Option[T]] { option =>
option.map(value =>
JsPath.createObj(path -> w.writes(value))
).getOrElse(JsPath.createObj(path -> JsNull))
}
}
And if you are using play framework :
implicit val totoWrites: Writes[Toto] = (
(JsPath \ "titre").write[String] and
(JsPath \ "option").writeJsonOption[String] and
(JsPath \ "descriptionPoste").writeNullable[String]
) (unlift(Toto.unapply))
implicit val totoReads: Reads[Toto] = (
(JsPath \ "titre").read[String] and
(JsPath \ "option").readNullable[String] and
(JsPath \ "descriptionPoste").readNullable[String]
) (Toto.apply _)
You may wrap your option and redefine serialization behavior:
case class MyOption[T](o: Option[T])
implicit def myOptWrites[T: Writes] = Writes[MyOption[T]](_.o.map(Json.toJson).getOrElse(JsNull))
CAVEATS:
1)this approach is good only for case classes used solely as a serialization protocol definition. If you are reusing some data model classes in controllers - define custom serializers for them not to pollute the model.
2)(related only to Option) if you use the same class for writes and reads. Play will require the wrapped fields to be present (possibly null) during deserialization.
P.S.: Failed to do the same with type tagging. Compiler error is like
No instance of play.api.libs.json.Writes is available for tag.<refinement> (given the required writes were explicitly defined).
Looks like Play's macro fault.

Parsing a List of Models in Play! 2.1.x

Given the JSON...
[ {"ID": "foo"}, {"ID": "bar"} ]
Represented with case classes...
case class Example(models: List[Model])
case class Model(id: String)
I attempt the following which fails with overloaded method value read with alternatives.
trait JsonReader {
implicit val modelReads: Reads[Model] = (__ \ "name").read[String](Model)
implicit val exampleReads: Reads[Example] = JsPath.read[List[Model]](Example)
def get (response: Response) = response.json.as[Example]
}
What is the correct way to parse this?
For a strange reason I did not find an elegant solution to read a json model with only one value. For 2 and more values you may write:
implicit val reader = (
(__ \ 'id).read[Long] and
(__ \ 'field1).read[String] and
(__ \ 'field2).read[String])(YourModel.apply _)
For a json with 1 field try using something like that:
implicit val reader = new Reads[Model] {
def reads(js: JsValue): JsResult[Model] = {
JsSuccess(Model((js \ "name").as[String]))
}
}
This should work but doesn't look nice :(

Play 2.1-RC2: Read keys from JSON

I try to migrate my web application from Play 2.0.4 to Play 2.1-RC2.
I have JSON data with a list of unknown keys (key1, key2) like this:
{description: "Blah",
tags: [
key1: ["value1", "value2"],
key2: ["value3"]
]
}
I want to store the data from the JSON in a List of Metatags. In Play 2.0.4 I have used something like this to read the tags-list:
def readMetatags(meta: JsObject): List[Metatag] =
meta.keys.toList.map(x => Metatag(x, (meta \ x).as[List[String]])
Now I want to use the new Play 2.1-JSON-API (Prototype):
import play.api.libs.json._
import play.api.libs.functional.syntax._
object Metatags {
implicit val metatagsRead: Read[Metatags] = (
(JsPath \ "description").read[String] and
(JsPath \ "tags").read[List[Metatag]]
)(Metatags.apply _, unlift(Metatags.unapply _))
implicit val metatagRead: Read[Metatag] = (
JsPath().key. ?? read[String] and // ?!? read key
JsPath().values. ?? read[List[String]] // ?!? read value list
)(Metatag.apply _, unlift(Metatag.unapply _))
}
case class Metatags(description: String, tags: List[Metatag])
case class Metatag(key: String, data: List[String])
How can I read the keys from the JSON?
This is a solution with a custom reader for the MetaTag class. The read just convert the JsValue to a JsObject, which have the useful fieldSet method.
For MetaTags, the macro inception works perfectly
object Application extends Controller {
case class MetaTags(description: String, tags: List[MetaTag])
case class MetaTag(key: String, data: List[String])
implicit val readMetaTag = Reads(js =>
JsSuccess(js.as[JsObject].fieldSet.map(tag =>
MetaTag(tag._1, tag._2.as[List[String]])).toList))
implicit val readMetaTags = Json.reads[MetaTags]
def index = Action {
val json = Json.obj(
"description" -> "Hello world",
"tags" -> Map(
"key1" -> Seq("key1a", "key1b"),
"key2" -> Seq("key2a"),
"key3" -> Seq("Key3a", "key3b", "key3c")))
val meta = json.as[MetaTags]
Ok(meta.tags.map(_.data.mkString(",")).mkString("/"))
// key1a,key1b/key2a/Key3a,key3b,key3c
}
}