In Play 2.1 we use something like below to get a Creature Object out of a JSON through reads.
implicit val creatureReads = (
(__ \ "name").read[String] and
(__ \ "isDead").read[Boolean] and
(__ \ "weight").read[Float]
)(Creature.apply _)
Being relative new in Scala, I'm trying to understand if there is any other way to build the Creature object without using the Apply method? Would it be possible to have an anonymous function to create the object instead of relying on the apply?
I have use cases where most of the fields in my objects could be missing, but I would like to still build the object out of what I have. Is it better to just define one READ for the object and use readnullable for each of the fields?
I also could have complex conditionals, so would it be cleaner to just define custom functions to build it instead of trying to capture all cases in one Reader?
Yes of course, the apply method is just a method that takes all the case classes' parameters. This roughly translates to the following:
implicit val creatureReads = (
(__ \ "name").read[String] and
(__ \ "isDead").read[Boolean] and
(__ \ "weight").read[Float]
)((name: String, isDead: Boolean, weight: Float) => new Creature(name, isDead, weight))
For missing fields you should indeed use readNullable and wrap your classes fields to Option. If there are sensible defaults for your optional fields, you can use orElse(Reads.pure(value)) instead.
Let's say weight is optional and isDead is false by default:
implicit val creatureReads = (
(__ \ "name").read[String] and
(__ \ "isDead").read[Boolean].orElse(Reads.pure(false)) and
(__ \ "weight").readNullable[Float]
)(Creature.apply _)
Sometimes you don't even want to read something from JSON. In that case, one possibility is passing the value explicitly:
def creatureReads(createdAt: DateTime) = (
(__ \ "name").read[String] and
(__ \ "isDead").read[Boolean].orElse(Reads.pure(false)) and
(__ \ "weight").readNullable[Float] and
(__ \ "createdAt").read(createdAt)
)(Creature.apply _)
I find this to be much more readable:
implicit val createReads = new Reads[Creature] {
override def reads(json: JsValue): JsResult[Creature] = {
val creature = Creature(
name = (json \ "name").as[String],
isDead = (json \ "isDead").as[Boolean],
weight = (json \ "weight").as[Float]
)
JsSuccess(creature)
}
}
Related
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 _)
I need to serialize a String and an Option[Boolean]:
val myWrites = (
(__ \ "box").write(
(
(__ \ "name").write[String] ~
(__ \ "default").writeNullable[Boolean]
).tupled
)
)
If Option[Boolean] is Some then I'd expect
{
"box": {
"name": "John",
"default": true
}
}
... while if Option[Boolean] is None I'd expect
{
"box": {
"name": "John"
}
}
Given the following variables...
val name = "John"
val default = Some(true)
... how do I pass them to the Writes? I've tried this:
myWrites.writes(name, defaul)
... but it doesn't compile:
No Json serializer found for type play.api.libs.functional.FunctionalBuilder[play.api.libs.json.OWrites]#CanBuild2[String,Option[Boolean]].
Try to implement an implicit Writes or Format for this type.
[error] (__ \ "box").write(
I think its just a typo in your writes. you have defaul vs default
I was able to use
import play.api.libs.json._
import play.api.libs.functional.syntax._
val myWrites = (
(__ \ "box").write(
(
(__ \ "name").write[String] ~
(__ \ "default").writeNullable[Boolean]
).tupled
)
)
myWrites.writes("hi",Some(true))
and I got back
res0: play.api.libs.json.JsObject = {"box":{"name":"hi","default":true}}
I have this scenario im getting a list of Facebook feed(facebook api) to a list of json objects.
Than i want to deserialize to a list of class object.
First question is ,which data structure will be best practices ? (cass class,tratis or regular class)
This is how i did it , i created a list of Comments in the Post case class
case class Post(id: String, fromId: String, fromName: String, message: String, fullUrl: String, createdTime: String, updateTime: String ,List[Comment]) {
}
case class Comment(id: String, fromId: String, fromName: String, message: String, creationTime: String, likeCount: Int)
Then im mapping it like that
implicit val post = (
(__ \ "id").read[String] and
(__ \ "from").\("id").read[String] and
(__ \ "from").\("name").read[String] and
(__ \ "message").read[String] and
(__ \ "actions").\\("link").read[String] and
(__ \ "created_time").read[String] and
(__ \ "updated_time").read[String]
)(Post)
The second question is how can i map the list ??
thanks,
miki
Case classes looks good in this case.
You need to define the same implicit reader for comments and then change you reader for posts something like that:
implicit val post = 2
(__ \ "id").read[String] and
(__ \ "from").\("id").read[String] and
(__ \ "from").\("name").read[String] and
(__ \ "message").read[String] and
(__ \ "actions").\\("link").read[String] and
(__ \ "created_time").read[String] and
(__ \ "updated_time").read[String] and
(__ \ "comments").reads[List[Comment]]
)(Post)
I'm trying to understand the following piece of code in the Activator reactive maps example. I would appreciate if someone can explain the ( __ \ "event") and ~ part of code.
object UserPositions {
implicit def userPositionsFormat: Format[UserPositions] = (
(__ \ "event").format[String] ~
(__ \ "positions").format[FeatureCollection[LatLng]]
).apply({
case ("user-positions", positions) => UserPositions(positions)
}, userPositions => ("user-positions", userPositions.positions))
}
It is converting a Json object into Scala object and vice versa. (__ \ "event") means "event" element of Json object, and ~ means joining the elements into a tuple.
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)