Json transformers - new field from 2 existing ones - scala

Using the json API, how can I write a transformer for the following:
{
"name":"John Doe",
"number":22
}
to
{
"name":"John Doe",
"number":22,
"slug":"John-Doe-22"
}
This is doable using string manipulation, but how do I pick the values from the 2 fields and apply that to them?

You can do this:
val json = Json.obj(
"name" -> JsString("John Doe"),
"number" -> JsNumber(22)
)
val transformed =
json.transform(
__.json.update(
(__ \ 'slug).json.put(JsString(
(json \ "name").as[String].replace(' ', '-') + "-" + (json \ "number").as[Int]
))
)
).get
Ok(transformed.toString())
//{"name":"John Doe","number":22,"slug":"John-Doe-22"}
Here is nice post about play json trasformations.

Related

How filter a JSLookup/JSObject with the Scala Play library

Say I have some params coming from our API clients like this:
val params = (request \ "params")
I want to filter them and remove certain key/values. Like if I get:
{
"foo": "bar",
"hello": "world"
}
I want to filter it to
{
"foo": "bar"
}
Here's my WIP code but, as more advanced Scala people will probably tell right away, it doesn't work.
val params = (request \ "params").get.as[List[JsObject]]
val blacklistedParams = Seq("foo")
val approvedParams = params.filter((param: JsObject) => {
!blacklistedParams.contains(param)
})
That first line always fails. I've tried doing .get.as in all sorts of types but always get errors. I'm still new to Scala and types in general.
I think I figured out a way:
val params = (request \ "params").get.as[Map[String, JsValue]]
val blacklistedParams = Seq("foo")
val approvedParams = params.filter((param) => {
!blacklistedParams.contains(param._1)
})
My only annoyance with this method is that ._1. To me its not very clear for the next person that this is the key of the key/val pair of the params.
You can use -
val filtered = (request \ "params").as[JsObject] - "hello"
Full example:
def index = Action{
val json = Json.obj(
"params" -> Json.obj(
"foo" -> "bar",
"hello" -> "world"))
val filtered = (json \ "params").as[JsObject] - "hello"
Ok(filtered)
}
output:
{
foo: "bar"
}

Play Framework: How to serialize raw fields to JSON with nested elements

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

ReactiveMongo: Manage Sequence field

I'm developing an app with Play Framework and ReactiveMongo.
I want to write CRUD operations for a model field with type of Seq[Entities]
Here is my model:
case class Person(_id: Option[BSONObjectID],
email: String,
password: String,
education: Option[Education])
object Lawyer {
implicit val accountWrites: Writes[Person] = (
(JsPath \ "_id").writeNullable[BSONObjectID] and
(JsPath \ "email").write[String] and
(JsPath \ "password").write[String] and
(JsPath \ "education").writeNullable[Education]
)(unlift(Person.unapply))
implicit val accountReads: Reads[Person] = (
(JsPath \ "_id").readNullable[BSONObjectID].map(_.getOrElse(BSONObjectID.generate)).map(Some(_)) and
(JsPath \ "email").read[String] and
(JsPath \ "password").read[String] and
(JsPath \ "education").readNullable[Education]
)(Person.apply _)
case class Education(status: String, certificates: Option[Seq[Certificate]])
object Education {
implicit val educationFormat: Format[Education] = (
(JsPath \ "status").format[String] and
(JsPath \ "certificates").formatNullable[Seq[Certificate]]
)(Education.apply, unlift(Education.unapply))
}
case class Certificate(id: Option[String] = Some(Random.alphanumeric.take(12).mkString),
name: String,
licenseCode: Option[String],
link: Option[String],
date: Date)
object Certificate {
implicit val certificateFormat = Json.format[Certificate]
}
The questions are:
1) How I can POST the Certificate entity using a form?
Because when I use:
def createCertificate(email: String, certificate: Certificate) = {
val createCertificate = Json.obj(
"$set" -> Json.obj(
"education.certificates" -> certificate
)
)
collection.update(
Json.obj("email" -> email),
createCertificate
)
}
It creates object field {...} insted of array of objects [ {...}, ... ]
2) How I can DELETE the Certificate entity from the Seq by ID?
Thanks
1) I assume that you want createCertificate to add a single certificate to an (possibly empty) array of certificates instead of creating an array with a single certificate. In that case you can replace your $set operator with the $push operator:
val createCertificate = Json.obj(
"$push" -> Json.obj(
"education.certificates" -> certificate
)
)
2) Likewise, for removing an element you can use the $pull operator:
def removeCertificate(email: String, certificateId: String) = {
val removeCert = Json.obj(
"$pull" -> Json.obj(
"education.certificates" -> Json.obj("id" -> certificateId)
)
)
collection.update(
Json.obj("email" -> email),
removeCert
)
}

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?

How to parse json with a single field in Playframework2?

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