Processing JSON Response in Scala play WS - json

I would like to process the following JSON response:
[
{
"name":"xxx1",
"format":"xxx2",
"type":"xxx3",
"url":"xxx4"
},
{
"name":"yyy1",
"format":"yyy2",
"type":"yyy3",
"url":"yyy4"
}
]
I am tying to write the related code, but I am stuck here:
case class Repository(repoName: String, repoFormat: String, repoType: String, repoURL: String)
case class RepositoryList(repositoryList: List[Repository])
implicit val repoReads = Reads[Repository] = (
(JsPath \ "name").read[String] and
(JsPath \ "format").read[String] and
(JsPath \ "type").read[String] and
(JsPath \ "url").read[String]
)(Repository.apply _)
implicit val repoListReads = Reads[RepositoryList] = (
(???).read[Seq[Repository]]
)(RepositoryList.apply _)
request.get().map(response => Ok((response.json).validate[RepositoryList]))
What comes to the "???" part? It is basically a list of JSONs, there is no attribute on that level.
Thanks in advance!

A pragmatic solution would be just to use List[Repository] and then create a RepositoryList:
request.get()
.map(response =>
Ok(
(response.json).validate[List[Repository]]
.map(RepositoryList)
)
)
Check scalafiddle

In terms of using validate[RepositoryList] and avoiding the extra map after validate, using Reads.list composition would do the trick. So the ??? could be replaced with
implicit val repoListReads: Reads[RepositoryList] = Reads.list[Repository].map(RepositoryList)
and you can call
response.json.validate[RepositoryList]

Related

JSON / BigInt Json deserializer in play / scala

I am trying to read some JSON String from request and convert them in case class in Play/Scala based REST application.
My code is something like ...
implicit val memberRead: Reads[MemberInfo] = (
(JsPath \ "memberId").readNullable[BigInt] and
(JsPath \ "firstName").read[String] and
(JsPath \ "lastName").read[String] and
(JsPath \ "janrainUUID").readNullable[String] and
(JsPath \ "phones").read[Seq[MemberPhone]] and
(JsPath \ "address").read[Seq[MemberAddress]]
)(MemberInfo.apply _)
implicit val addressRead: Reads[MemberAddress] = (
(JsPath \ "addressId").readNullable[BigInt] and
(JsPath \ "addressType").read[String] and
(JsPath \ "address").read[String] and
(JsPath \ "memberId").read[BigInt]
)(MemberAddress.apply _)
implicit val phoneRead: Reads[MemberPhone] = (
(JsPath \ "phoneId").readNullable[BigInt] and
(JsPath \ "phoneNumber").read[String] and
(JsPath \ "phoneType").read[String] and
(JsPath \ "primaryInd").read[String] and
(JsPath \ "memberId").read[BigInt]
)(MemberPhone.apply _)
But I am getting some compilation error(For all three readNullable[BigInt], memberid in memberRead, addressId in addressRead and phoneId in phoneRead ). Error is ...
No Json deserializer found for type BigInt. Try to implement an implicit Reads or Format for this type.
My Case class are some like this ...
case class MemberInfo(memberId : Option[BigInt],firstName : String, lastName : String,janrainUUID :Option[String] , phones : Seq[MemberPhone],address : Seq[MemberAddress])
case class MemberAddress(addressId:Option[BigInt],addressType:String,address:String,memberId:BigInt)
case class MemberPhone(phoneId : Option[BigInt], phoneNumber:String,phoneType:String,primaryInd:String,memberId:BigInt)
for janrainUUID :Option[String] I am not getting any compilation error , but for BigInt I am getting "No Json deserializer found for type BigInt"
Any one can explain why I am getting this error for BigInt and How can I resolve those? Actually those are PK value when I will do the DB operation for those, so they never may come with request. Is there any way to express that in play/scala like #ignore annotation in jersey.
Any help will be appreciated , thanks a lot...
You need to define serializers for BigInt in the following way:
implicit val BigIntWrite: Writes[BigInt] = new Writes[BigInt] {
override def writes(bigInt: BigInt): JsValue = JsString(bigInt.toString())
}
implicit val BigIntRead: Reads[BigInt] = Reads {
case JsString(value) => JsSuccess(scala.math.BigInt(value))
case JsNumber(value) => JsSuccess(value.toBigInt())
case unknown => JsError(s"Invalid BigInt")
}
Just add this before memberRead serializer and you are good to go and also add error handling for invalid BigInt.
play-json doesn't provide a Reads[BigInt]. It only provides a Reads[BigDecimal].
You can either write your own Reads[BigInt]:
implicit val bigIntReads: Reads[BigInt] = implicitly[Reads[BigDecimal]].map(_.toBigInt())
or use play's Reads[BigDecimal] and transform the result:
implicit val memberRead: Reads[MemberInfo] = (
(JsPath \ "memberId").readNullable[BigDecimal].map(_.toBigInt()) and
...
Edit: Both above solutions have the advantage of not re-inventing the wheel, they build on some well-tested infrastructure provided by play-json. As such they provide benefits that other solutions proposed for this question do not, mainly the correct handling of json string as well as numbers.
You can implement Format like this and use it in your companion object as implicit val:
import play.api.libs.json._
import play.api.libs.functional.syntax._
import scala.util.Try
object BigIntFormat extends Format[BigInt] {
override def reads(json: JsValue): JsResult[BigInt] = json match {
case JsNumber(n) => Try(JsSuccess(n.toBigInt)).getOrElse {
JsError(JsPath() -> JsonValidationError(s"error.expected.numeric(as BigInt), but got '$json'"))
}
case JsString(s) => Try(JsSuccess(BigInt(s))).getOrElse {
JsError(JsPath() -> JsonValidationError(s"error.expected.string(as BigInt), but got '$json'"))
}
case _ => JsError(JsPath() -> JsonValidationError("error.expected.string"))
}
override def writes(o: BigInt): JsValue = JsString(o.toString)
}
case class SomethingWithBigInt(id: BigInt, str: String)
object SomethingWithBigInt {
implicit val bigIntFormatter = BigIntFormat
implicit lazy val format: Format[SomethingWithBigInt] = ({
(JsPath \ "id").format[BigInt] and
(JsPath \ "str").format[String]
})(SomethingWithBigInt.apply, unlift(SomethingWithBigInt.unapply))
}

playframework - Best way to trim a json values

I am trying to figure out the best and elegant way to tim values on an in coming json.
So for example I have the following json:
{
"firstName": " foo",
"lastName": "bar "
}
With the following definitions:
case class Someone(firstName:String, lastName: String)
object Someone{
implicit val someoneReads: Reads[Someone] = (
(JsPath \ "firstName").read[String] and
(JsPath \ "lastName").read[String]
)(Someone.apply _)
}
Is there a way to trim the json while reading it? or I need to write a transformer for that? and if I do, how to write it so it will be generic to trip every json I will provide?
Thanks!
Use map(_.trim) for read[String] for trim string (universal solution)
implicit val someoneReads: Reads[Someone] = (
(JsPath \ "firstName").read[String].map(_.trim) and
(JsPath \ "lastName").read[String].map(_.trim)
)(Someone.apply _)
You can implement own Reads[String] with trimmed string also
def trimmedString(path: JsPath): Reads[String] = Reads.at[String](path).map(_.trim)
implicit val someoneReads: Reads[Someone] = (
trimmedString(JsPath \ "firstName") and trimmedString(JsPath \ "lastName")
)(Someone.apply _)
For a more familiar view of code you may implement implicit conversion
import scala.language.implicitConversions
class JsPathHelper(val path: JsPath) {
def trimmedString: Reads[String] = Reads.at[String](path).map(_.trim)
}
implicit def toJsPathHelper(path: JsPath): JsPathHelper = new JsPathHelper(path)
implicit val someoneReads: Reads[Someone] = (
(JsPath \ "firstName").trimmedString and
(JsPath \ "lastName").trimmedString
)(Someone.apply _)
You can specify your own reads[String] based on the default one, and then use macros:
object Someone {
implicit val trimStringReads: Reads[String] = Reads.StringReads.map(_.trim)
implicit val someoneReads: Reads[Someone] = Json.reads[Someone]
}

Serialize only specific attributes using Writes trait with unapply

Lets imagine I have a case class like this:
case class Product(ean: Long, name: String, description: String)
and I want so serialize objects of this class to Json, I can implement the Writes trait like this:
implicit val productWrites: Writes[Product] = (
(JsPath \ "ean").write[Long] and
(JsPath \ "name").write[String] and
(JsPath \ "description").write[String]
)(unlift(Product.unapply))
This works fine if I want to serialize all the attributes of the object. Now lets say I don't want to serialize the ean. I tried something like this:
implicit val productWrites: Writes[Product] = (
(JsPath \ "name").write[String] and
(JsPath \ "description").write[String]
)(unlift(Product.unapply))
This doesn't seem to work since one needs to use all the fields/attributes that the unapply method returns.
Is there a way to make the second serialization method work with only the attributes that I want to serialize or do I have to use something like this:
implicit object ProductWrites extends Writes[Product] {
def writes(p: Product) = Json.obj(
"name" -> Json.toJson(p.name),
"description" -> Json.toJson(p.description)
)
}
Is this the only way?
unlift(Product.unapply) has a type Product => (Long, String, String).
In this case, the argument should have a type Product => (String, String). You can write a function literal like following.
implicit val productWrites: Writes[Product] = (
(JsPath \ "name").write[String] and
(JsPath \ "description").write[String]
)(p => (p.name, p.description))
I think your last example is the way to go. Here's another way of doing the same thing using an implicit val instead of an implicit object:
implicit val productWrites: Writes[Product] = Writes { p =>
Json.obj(
"name" -> Json.toJson(p.name),
"description" -> Json.toJson(p.description)
)
}

Playframework - JSON parsing object with single field - definition issue

I cannot find a way how to make it work when deserialized object has single field - I cannot compile the code. Seems that and operator does some transformation and I cannot find a method to call to do the same.
I have following json:
{"total": 53, "max_score": 3.2948244, "hits": [
{
"_index": "h",
"_type": "B",
"_id": "3413569628",
"_score": 3.2948244,
"_source": {
"fotky": [
{
"popisek":" ",
"localFileSystemLocation":" ",
"isMain": true,
"originalLocation": ""
}
]
}
}
]
}
I try the following data model to de serialize to:
case class SearchLikeThisResult(total: Int, max_score: Double, hits: Seq[Hits])
case class Hits(_index: String, _type: String, _id: String, _score: Double, _source: Source)
case class Source(fotky: Seq[Photo])
case class Photo(isMain: Boolean, originalLocation: Option[String], localFileSystemLocation: Option[String], popisek: Option[String])
Implicit reads as follows:
object SearchLikeThisHits {
import play.api.libs.functional.syntax._
implicit val photoReads: Reads[Photo] = (
(JsPath \ "isMain").read[Boolean] and
(JsPath \ "originalLocation").readNullable[String] and
(JsPath \ "localFileSystemLocation").readNullable[String] and
(JsPath \ "popisek").readNullable[String]
)(Photo.apply _)
implicit val sourceReads: Reads[Source] = (
(JsPath \ "fotky").read[Seq[Photo]]
)(Source.apply _)
implicit val hitsReads: Reads[Hits] = (
(JsPath \ "_index").read[String] and
(JsPath \ "_type").read[String] and
(JsPath \ "_id").read[String] and
(JsPath \ "_score").read[Double] and
(JsPath \ "_source").read[Source]
)(Hits.apply _)
implicit val searchLikeThisResult: Reads[SearchLikeThisResult] = (
(JsPath \ "total").read[Int] and
(JsPath \ "max_score").read[Double] and
(JsPath \ "hits").read[Seq[Hits]]
)(SearchLikeThisResult.apply _)
}
What I am really struggling with is under the _source:
implicit val sourceReads: Reads[Source] = (
(JsPath \ "fotky").read[Seq[Photo]]
)(Source.apply _)
where read is reported as unkown symbol - in other cases and performs some transformation.
Inline definition doesn't help either.
Does anybody faced this before?
The fancy applicative builder syntax (and, etc.) is nice, but it can obscure the fact that Reads is monadic and also works perfectly well with map, flatMap, for-comprehensions, etc.
So while the applicative builder syntax doesn't work with single values, plain old map does:
implicit val sourceReads: Reads[Source] =
(JsPath \ "fotky").read[Seq[Photo]].map(Source(_))
The key here is that (JsPath \ "fotky").read[Seq[Photo]] is a Reads[Seq[Photo]], and you want a Reads[Source]. map gives you a way to get from one to the other, just as you could use it to transform an Option[Seq[Photo]] into an Option[Source, for example.
You could save yourself some trouble by making use of the Json.reads to automatically generate your Reads (provided the case class is defined exactly like the Json objects - which is your case).
implicit val photoReads = Json.reads[Photo]
implicit val sourceReads = Json.reads[Source]
implicit val hitsReads = Json.reads[Hits]
implicit val searchResultReads = Json.reads[SearchLikeThisResult]
For more information, see https://www.playframework.com/documentation/2.1.1/ScalaJsonInception

Custom Json Writes with combinators - not all the fields of the case class are needed

I'm trying to write a custom Json serializer in play for a case class but I don't want it to serialize all the fields of the class. I'm pretty new to Scala, so that is surely the problem but this is what I tried so far:
case class Foo(a: String, b: Int, c: Double)
Now the default way of doing this, as far as I saw in the examples is:
implicit val fooWrites: Writes[Foo] = (
(__ \ "a").write[String] and
(__ \ "b").write[Int]
(__ \ "c").write[Double]
) (unlift(Foo.unapply))
But what if I want to omit "c" from the Json output? I've tried this so far but it doesn't compile:
implicit val fooWritesAlt: Writes[Foo] = (
(__ \ "a").write[String] and
(__ \ "b").write[Int]
) (unlift({(f: Foo) => Some((f.a, f.b))}))
Any help is greatly appreciated!
If you are using Playframework 2.2 (not sure about earlier versions, but it should work as well) try this:
implicit val writer = new Writes[Foo] {
def writes(foo: Foo): JsValue = {
Json.obj("a" -> foo.a,
"b" -> foo.b)
}
}
What I usually do is convert a field to None and use writeNullable on it:
implicit val fooWrites: Writes[Foo] = (
(__ \ "a").write[String] and
(__ \ "b").write[Int]
(__ \ "c").writeNullable[Double].contramap((_: Double) => None)
) (unlift(Foo.unapply))
Instead of specifying a combinator which produces an OWrites instance, one can directly construct an OWrites just as well:
val ignore = OWrites[Any](_ => Json.obj())
implicit val fooWrites: Writes[Foo] = (
(__ \ "a").write[String] and
(__ \ "b").write[Int] and
ignore
) (unlift(Foo.unapply))
This will ignore any value of the case class at that position and simply always return an empty JSON object.
This is very easy to do with the Play's JSON transformers:
val outputFoo = (__ \ 'c).json.prune
and then apply transform(outputFoo) to your existing JsValue:
val foo: Foo
val unprunedJson: JsValue = Json.toJson(foo)
unprunedJson.transform(outputFoo).map { jsonp =>
Ok(Json.obj("foo" -> jsonp))
}.recoverTotal { e =>
BadRequest(JsError.toFlatJson(e))
}
see here http://mandubian.com/2013/01/13/JSON-Coast-to-Coast/
What version of Play are you using? fooWritesAlt compiles for me using 2.1.3. One thing to note is that I needed to explicitly use the writes object to write the partial JSON object, i.e.
fooWritesAt.writes(Foo("a", 1, 2.0))
returns
{"a": "a", "b": 2}