How to use different names when mapping JSON array to Scala object using combinators - json

Given a JSON array like this one:
{
"success": true,
"data": [
{
"id": 600,
"title": "test deal",
"e54cbe3a434d8e6": 54
},
{
"id": 600,
"title": "test deal",
"e54cbe3a434d8e6": 54
},
],
"additional_data": {
"pagination": {
"start": 0,
"limit": 100,
"more_items_in_collection": false
}
}
}
In my Play 2.2.2 application, using the Scala JSON Reads Combinator, everything works going this way:
implicit val entityReader = Json.reads[Entity]
val futureJson: Future[List[Entity]] = futureResponse.map(
response => (response.json \ "data").validate[List[Entity]].get
The problem now is the key named 'e54cbe3a434d8e6' which I would like to name 'value' in my object:
// This doesn't work, as one might expect
case class Entity(id: Long, title: String, e54cbe3a434d8e6: Long)
// I would like to use 'value' instead of 'e54cbe3a434d8e6'
case class Entity(id: Long, title: String, value: Long)
There is vast information about the combinators here and here but I only want to use a fieldname which is different from the key name in the JSON array. Can some one help me to find a simple way?
I suppose it has got something to do with JSON.writes?!

One simple way without trying to apply transformations on json itself is to define a custom Reads in such a way to handle this:
val json = obj(
"data" -> obj(
"id" -> 600,
"title" -> "test deal",
"e54cbe3a434d8e6" -> 54))
case class Data(id: Long, title: String, value: Int)
val reads = (
(__ \ "id").read[Long] ~
(__ \ "title").read[String] ~
(__ \ "e54cbe3a434d8e6").read[Int] // here you get mapping from your json to Scala case class
)(Data)
def index = Action {
val res = (json \ "data").validate(reads)
println(res) // prints "JsSuccess(Data(600,test deal,54),)"
Ok(json)
}
Another way is to use combinators like this:
... the same json and case class
implicit val generatedReads = reads[Data]
def index = Action {
val res = (json \ "data").validate(
// here we pick value at 'e54cbe3a434d8e6' and put into brand new 'value' branch
__.json.update((__ \ "value").json.copyFrom((__ \ "e54cbe3a434d8e6").json.pick)) andThen
// here we remove 'e54cbe3a434d8e6' branch
(__ \ "e54cbe3a434d8e6").json.prune andThen
// here we validate result with generated reads for our case class
generatedReads)
println(res) // prints "JsSuccess(Data(600,test deal,54),/e54cbe3a434d8e6/e54cbe3a434d8e6)"
Ok(prettyPrint(json))
}

Related

Play JSON Reads[T]: split a JsArray into multiple subsets

I have a JSON structure that contains an array of events. The array is "polymorphic" in the sense that there are three possible event types A, B and C:
{
...
"events": [
{ "eventType": "A", ...},
{ "eventType": "B", ...},
{ "eventType": "C", ...},
...
]
}
The three event types don't have the same object structure, so I need different Reads for them. And apart from that, the target case class of the whole JSON document distinguishes between the events:
case class Doc(
...,
aEvents: Seq[EventA],
bEvents: Seq[EventB],
cEvents: Seq[EventC],
...
)
How can I define the internals of Reads[Doc] so that the json array events is split into three subsets which are mapped to aEvents, bEvents and cEvents?
What I tried so far (without being succesful):
First, I defined a Reads[JsArray] to transform the original JsArray to another JsArray that only contains events of a particular type:
def eventReads(eventTypeName: String) = new Reads[JsArray] {
override def reads(json: JsValue): JsResult[JsArray] = json match {
case JsArray(seq) =>
val filtered = seq.filter { jsVal =>
(jsVal \ "eventType").asOpt[String].contains(eventTypeName)
}
JsSuccess(JsArray(filtered))
case _ => JsError("Must be an array")
}
}
Then the idea is to use it like this within Reads[Doc]:
implicit val docReads: Reads[Doc] = (
...
(__ \ "events").read[JsArray](eventReads("A")).andThen... and
(__ \ "events").read[JsArray](eventReads("B")).andThen... and
(__ \ "events").read[JsArray](eventReads("C")).andThen... and
...
)(Doc.apply _)
However, I don't know how to go on from here. I assume the andThen part should look something like this (in case of event a):
.andThen[Seq[EventA]](EventA.reads)
But that doesn't work since I expect the API to create a Seq[EventA] by explicitly passing a Reads[EventA] instead of Reads[Seq[EventA]]. And apart from that, since I've never got it running, I'm not sure if this whole approach is reasonable in the first place.
edit: in case the original JsArray contains unknown event types (e.g. D and E), these types should be ignored and left out from the final result (instead of making the whole Reads fail).
put implicit read for every Event type like
def eventRead[A](et: String, er: Reads[A]) = (__ \ "eventType").read[String].filter(_ == et).andKeep(er)
implicit val eventARead = eventRead("A", Json.reads[EventA])
implicit val eventBRead = eventRead("B", Json.reads[EventB])
implicit val eventCRead = eventRead("C", Json.reads[EventC])
and use Reads[Doc] (folding event list to separate sequences by types and apply result to Doc):
Reads[Doc] = (__ \ "events").read[List[JsValue]].map(
_.foldLeft[JsResult[ (Seq[EventA], Seq[EventB], Seq[EventC]) ]]( JsSuccess( (Seq.empty[EventA], Seq.empty[EventB], Seq.empty[EventC]) ) ){
case (JsSuccess(a, _), v) =>
(v.validate[EventA].map(e => a.copy(_1 = e +: a._1)) or v.validate[EventB].map(e => a.copy(_2 = e +: a._2)) or v.validate[EventC].map(e => a.copy(_3 = e +: a._3)))
case (e, _) => e
}
).flatMap(p => Reads[Doc]{js => p.map(Doc.tupled)})
it will create Doc in one pass through events list
JsSuccess(Doc(List(EventA(a)),List(EventB(b2), EventB(b1)),List(EventC(c))),)
the source data
val json = Json.parse("""{"events": [
| { "eventType": "A", "e": "a"},
| { "eventType": "B", "ev": "b1"},
| { "eventType": "C", "event": "c"},
| { "eventType": "B", "ev": "b2"}
| ]
|}
|""")
case class EventA(e: String)
case class EventB(ev: String)
case class EventC(event: String)
I would model the fact that you store different event types in your JS array as a class hierarchy to keep it type safe.
sealed abstract class Event
case class EventA() extends Event
case class EventB() extends Event
case class EventC() extends Event
Then you can store all your events in a single collection and use pattern matching later to refine them. For example:
case class Doc(events: Seq[Event]) {
def getEventsA: Seq[EventA] = events.flatMap(_ match {
case e: EventA => Some(e)
case _ => None
})
}
Doc(Seq(EventA(), EventB(), EventC())).getEventsA // res0: Seq[EventA] = List(EventA())
For implementing your Reads, Doc will be naturally mapped to the case class, you only need to provide a mapping for Event. Here is what it could look like:
implicit val eventReads = new Reads[Event] {
override def reads(json: JsValue): JsResult[Event] = json \ "eventType" match {
case JsDefined(JsString("A")) => JsSuccess(EventA())
case JsDefined(JsString("B")) => JsSuccess(EventB())
case JsDefined(JsString("C")) => JsSuccess(EventC())
case _ => JsError("???")
}
}
implicit val docReads = Json.reads[Doc]
You can then use it like this:
val jsValue = Json.parse("""
{
"events": [
{ "eventType": "A"},
{ "eventType": "B"},
{ "eventType": "C"}
]
}
""")
val docJsResults = docReads.reads(jsValue) // docJsResults: play.api.libs.json.JsResult[Doc] = JsSuccess(Doc(List(EventA(), EventB(), EventC())),/events)
docJsResults.get.events.length // res1: Int = 3
docJsResults.get.getEventsA // res2: Seq[EventA] = List(EventA())
Hope this helps.

Add optional property with json transformers in playframework 2.4

I can't understand, how can i add optional property with json transformer.
I want merge two json objects (list and calendars) without or with dynamic list of properties (for example without owner):
calendar1 = {id:1, name: "first", description:"my first calendar", owner: 1}
calendar2 = {id:2, name: "second", owner: 1}
list = [{id: 1, settings: []}, {id: 2, settings: []}]
and result must be
{calendars:
[
{id:1, name: "first", description:"my first calendar", settings: []},
{id:2, name: "second", settings: []}
]
}
I'll assume the following json trees
val calendar1 = Json.parse("""{"id":1, "name": "first", "description":"my first calendar", "owner": 1}""")
val calendar2 = Json.parse("""{"id":2, "name": "second", "owner": 1}""")
You need to add settings to each calendar, then remove the owner if it exists.
Putting a value in branch settings is explained in the documentation
val addSettings = __.json.update((__ \ "settings").json.put(Json.arr()))
Dropping the owner is also explained
val removeOwner = (__ \ "owner").json.prune
Now you can define the transformer to be applied to each of your calendar object
val transformer = addSettings andThen removeOwner
With that in place there are multiple options depending on how your data is actually modeled. If you have a Seq of calendars as in
val calendars = Seq(calendar1, calendar2)
you can do
val normalizedCalendars = calendars.map(_.transform(transformer))
This gives you a Seq[JsResult[JsObject]] which you want to transform into a JsResult[Seq[JsObject]].
I am pretty sure there is a way to do it using play's functional syntax (see play.api.libs.functional and play.api.libs.functional.syntax) but this part of play is not well documented and I haven't gotten around to studying Applicatives yet even though I have a feel for what they do.
Instead, I rely on the following code inspired by scala's Future#sequence
def sequence[A, M[X] <: TraversableOnce[X]](in: M[JsResult[A]])(implicit cbf: CanBuildFrom[M[JsResult[A]], A, M[A]]): JsResult[M[A]] = {
val empty: JsResult[mutable.Builder[A, M[A]]] = JsSuccess(cbf(in))
in.foldLeft(empty) {(jracc,jrel) => (jracc,jrel) match {
case (JsSuccess(builder,_), JsSuccess(a,p)) =>JsSuccess(builder+=a, p)
case (ra#JsError(builderErrors), re#JsError(errors)) =>JsError.merge(ra, re)
case (JsSuccess(_,_), re#JsError(errors)) => re
case (ra#JsError(builderErrors), JsSuccess(_,_)) => ra
}} map (_.result())
}
With that you can write :
val calendarArray = sequence(normalizedCalendars).map(v=>Json.obj("calendars"->JsArray(v)))
which will give you a JsResult[JsObject]. As long as your original calendars are indeed JsObjects you will get a JsSuccess. You can verify the output structure with :
calendarArray.foreach(println)
which returns :
{"calendars":[{"id":1,"name":"first","description":"my first calendar","settings":[]},{"id":2,"name":"second","settings":[]}]}
which is the same as what you asked modulo some whitespace
{
"calendars":[
{"id":1,"name":"first","description":"my first calendar","settings":[]},
{"id":2,"name":"second","settings":[]}
]
}
Start with:
scala> case class Calendar(id:Int,name:String,description:Option[String],owner:Int)
defined class Calendar
scala> case class CalendarRow(id:Int,name:String,description:Option[String],settings:Seq[String]=Seq.empty)
defined class CalendarRow
scala> def append(calendars:Calendar*) = calendars.map(c => CalendarRow(c.id,c.name,c.description))
append: (calendars: Calendar*)Seq[CalendarRow]
scala> val calendar1 = Calendar(1,"first",Option("my first calendar"),1)
calendar1: Calendar = Calendar(1,first,Some(my first calendar),1)
scala> val calendar2 = Calendar(2, "second",None,1)
calendar2: Calendar = Calendar(2,second,None,1)
scala> val list = append(calendar1,calendar2)
list: Seq[CalendarRow] = ArrayBuffer(CalendarRow(1,first,Some(my first calendar),List()), CalendarRow(2,second,None,List()))
Many thanks to #Jean and comments in "Unveiling Play 2.1 Json API - Part 3 : JSON Transformers"
It's hard understanding things like and, andThen, andKeep, keepAnd in JSON transformers for me (I can not find any detailed descriptions with examples), but i found some templates for my question:
Optional property in JSON:
With Reader
(__ \ "id").json.pick[JsString].flatMap{
case id if id.equals(JsString(accountId)) =>
(__ \ "primary").json.put(JsBoolean(true))
case _ =>
Reads.pure(Json.obj())
}
With Json.obj()
(__ \ "id").json.pick[JsString].map{
case id if id.equals(JsString(accountId)) =>
Json.obj("primary" -> true)
case _ =>
Json.obj()
}

Simple CRUD with Scala and Playframework

Technology stack: Scala, PlayFramework 2.3.8, MongoDB ( Salat + casbah ).
I am developing some restful APIs, with simple CRUD.
The entity:
case class Company(
_id: ObjectId = new ObjectId,
user_id: Any = "",
cinema_id: Int = 0,
name: String = "",
status: String = "",
releaseId: Int, fileId: Int,
date: CompanyDate,
showTime: List[Int] = List())
case class CompanyDate(start: DateTime, end: DateTime)
I create Reads for creating the object from JSON:
def createFromJson(json: JsValue, user: User): Reads[Company] = {
val user_id = (for {
cinema_id <- (json \ "cinema_id").asOpt[Int]
cinema <- user.cinema.find(_.id == cinema_id)
} yield cinema.getUser(user._id.toString)
).getOrElse(0)
(
Reads.pure(new ObjectId) and
Reads.pure[Any](user_id).filter(ValidationError("You have no access to this cinema"))(_ != 0) and
(__ \ "cinema_id").read[Int] and
(__ \ "name").read[String](minLength[String](1) keepAnd maxLength[String](100)) and
Reads.pure[String]("new") and
(__ \ "release_id").read[Int] and
(__ \ "file_id").read[Int] and
(__ \ "date").read[CompanyDate] and
(__ \ "show_time").read[List[Int]](minLength[List[Int]](1))
)(Company.apply _).flatMap(company => Reads { _ =>
if (company.date.start isAfter company.date.end) {
JsError(JsPath \ "date" \ "start" -> ValidationError("Start < end"))
} else {
JsSuccess(company)
}
})
}
It took me create wrapped function to pass user to Reads, because some field ( user_id ) calculate from current user - client doesn't know this information.
It looks a little ugly, but works ok.
The Controller for create action is very simple, it takes json, casts it to class and saves to DB.
Next thing: Update. In update method i want reuse some validation cases ( check date.start < date.end, e.g. ), but i don't receive the full object. My client only sends me changed fields.
Some fields ( cinema_id and user_id ) can only be set when creating object, they can't be updated.
Example request (UPDATED):
PUT /api/company/1
{
"name": "Name",
"status": "delete"
}
How can i validate this json and update existing object?
What is the best approach to solve this task?

Play Framework: How to convert strings to numbers while validating JSON

Given the following JSON..
{
"ask":"428.00",
"bid":"424.20"
}
... I need to convert the values of ask and bid to numbers:
{
"ask": 428.00,
"bid": 424.20
}
To do that, I've created a validator that reads the string value and passes it to method toNumber, which validates and converts the given string:
def validate = (
((__ \ 'ask).json.pickBranch(Reads.of[JsString] <~ toNumber)) ~
((__ \ 'bid).json.pickBranch(Reads.of[JsString] <~ toNumber))
).reduce
private def toNumber(implicit reads: Reads[String]) = {
Reads[Double](js =>
reads.reads(js).flatMap { value =>
parse[Double](value) match {
case Some(number) => JsSuccess(number)
case _ => JsError(ValidationError("error.number", value))
}
}
)
}
The code above only validates the value but of course does not replace the original string with the converted number. How do I convert string values to numbers while validating?
EDIT
Just wanted to share the solution provided by Ben:
def validate = (
((__ \ 'ask).json.update(toNumber)) ~
((__ \ 'bid).json.update(toNumber))
).reduce
private def toNumber(implicit reads: Reads[String]) = {
Reads[JsNumber](js =>
reads.reads(js).flatMap { value =>
parse[Double](value) match {
case Some(number) => JsSuccess(JsNumber(number))
case _ => JsError(ValidationError("error.number", value))
}
}
)
}
If you make toNumber a Reads[JsNumber] instead of a Reads[Double] (simply by wrapping number in JsNumber), then you can use transform together with update:
val transformer = (__ \ "ask").json.update(toNumber)
val json = Json.parse(""" { "ask" : "44" } """)
json.transorm(transformer) //JsSuccess({"ask":44.0},/ask)
val json = Json.parse(""" { "ask" : "foo" } """)
json.transorm(transformer) //JsError(List((/ask,List(ValidationError(error.number,WrappedArray(foo))))))
In a sense, transformers are validators. Instead of checking if something is valid, and then transforming it, you can use transform to simply transform the value, and get a JsError if the transformation is invalid. Read more about transform here.

parsing a Json Array in play framework JsObject

I have the following Json:
{
"web-category" : "macaroons",
"sub-categories" : [
{ "name" : "pink" },
{ "name" : "blue" },
{ "name" : "green" }
]
}
I have got it in Play as a JsObject. So I can now successfully do the following:
//(o is the JsObject)
val webCat:Option[String] = (o \ "web-category").asOpt[String]
println(webCat.toString)
>> Some(macaroons)
So far, so good. But how do I access the array Json objects? I have this...
val subCats:Option[JsArray] = (o \ "sub-categories").asOpt[JsArray]
println(subCats.toString)
>> Some([{"name" : "blue"},{"name" : "green"},{"name" : "pink"}])
but what I need is to take the JsArray and get a List of all the names something like this:
List("blue", "green", "pink")
Don't know how to access the JsArray thusly.
my thanks for your help in this.
I'd argue that it's generally a good idea to move from JSON-land to native-Scala-representation-land as early as possible. If obj is your JsObject, for example, you can write this:
val subCategories = (obj \ "sub-categories").as[List[Map[String, String]]]
val names = subCategories.map(_("name"))
Or even:
case class Category(name: String, subs: List[String])
import play.api.libs.functional.syntax._
implicit val categoryReader = (
(__ \ "web-category").read[String] and
(__ \ "sub-categories").read[List[Map[String, String]]].map(_.map(_("name")))
)(Category)
And then:
obj.as[Category]
This latter approach makes error handling even cleaner (e.g. you can just replace as with asOpt at this top level) and composes nicely with other Reads type class instances—if you have a JsArray of these objects, for example, you can just write array.as[List[Category]] and get what you expect.
What Peter said, or:
(o \ "sub-categories" \\ "name").map(_.as[String]).toList
Something like this:
subCats.map( jsarray => jsarray.value.map(jsvalue => (jsvalue \ "name").as[String]).toList)
This will normally return a Option[List[String]]