Play Json Recursive Reads - json

I'm working on consuming an API which exposes an object at multiple layers within it's response. For example for some responses we get back a:
{
"error": {
"code": "123",
"description": "Description"
}
}
But in other situations it response with:
{
"data": [
{
"message_id": "123",
"error": {
"code": "123",
"description": "Description"
}
}
]
}
In both cases the error object is identical and in both case I don't actually care about the rest of the payload. I was hoping to use the \\ recursive JsPath operator, however the implementation below fails:
case class ErrorMessage(code: String, description: String)
implicit val errorMessageFormat = Json.format[ErrorMessage]
case class ErrorResponse(errors: Seq[ErrorMessage])
implicit val errorResponseFormat: Format[ErrorResponse] = Format(
(__ \\ "error").read[Seq[ErrorMessage]].map(ErrorResponse),
(__ \ "errors").write[Seq[ErrorMessage]].contramap((r: ErrorResponse) => r.errors)
)
This gives an error:
JsError(List((//error,List(ValidationError(List(error.expected.jsarray),WrappedArray())))))
I understand why: The (__ \\ "error") returns a Seq[JsValue], where as my read call is expecting a JsArray.
Is there a nice way a round this?

Since the first piece is already a Seq, just map the internal elements as you would map single objects. I'm not very familiar with the framework (I use Json4s myself mostly), but by your description sounds like
ErrorResponse((__ \\ "error").map(_.read[Seq[ErrorMessage]]))
Should be closer to what you want.(__ \\ "error") gives you a Seq of JsValues, map does something for every JsValue, read converts a single JsValue to an ErrorMessage and the resulting Seq[ErrorMessage] you pass to the ErrorResponse constructor.

So for anyone trying to do something similar, the below works.
val errorResponseReads = Reads[ErrorResponse] { value: JsValue =>
val errorsJsArray: JsArray = JsArray(value \\ "error")
errorsJsArray.validate[Seq[ErrorMessage]].map(ErrorResponse)
}
The format then becomes:
implicit val errorResponseFormat: Format[ErrorResponse] = Format(
errorResponseReads,
(__ \ "errors").write[Seq[ErrorMessage]].contramap((r: ErrorResponse) => r.errors)
)
Basically you need to define Reads that you use explicitly. In the reads you can then use the recursive search to return a Seq[JsValue] and then create a JsArray that can be validated as normal.
This works fine, it would be nice if we didn't have to define a separate Reads[T] though.

Related

play framework json reads from empty string to empty list

Hi everyone recently I faced an issue in converting json into my own data model.
I have a json format message which may contain an empty string:
{
"name" : "John Doe",
"hobbies": ""
}
or a list of hobby types:
{
"name" : "John Doe",
"hobbies": [{"name":"basketball"}]
}
And the following is my case class data model in scala play framework:
case class Person(name: String, hobbies: List[Hobby])
case class Hobby(name: String)
Right now I'm using the default json formatter but of course it's not working well when we have empty string as value.
implicit val HobbyJson= Json.format[Hobby]
implicit val PersonJson = Json.format[Person]
it will throw exception if the hobbies has a empty string. I want to convert it into an empty list when it's the empty string. I search the document Play provides but couldn't find infomation. Can anyone give some suggestions?
Thanks in advance.
As you mentioned, the default Format macros won't work for you here because of the inconsistent treatment of hobbies. So you need to implement your own Reads[Person] - here's how I'd do it:
object PersonJson {
implicit val hobbyConverter = Json.format[Hobby]
val personReads = new Reads[Person] {
override def reads(json: JsValue): JsResult[Person] = {
for {
personName <- (json \ "name").validate[String]
hobbies <- (json \ "hobbies").validate[JsValue]
} yield {
val maybeHobbyList = hobbies.validate[List[Hobby]].asOpt
Person(personName, maybeHobbyList.getOrElse(Nil))
}
}
}
implicit val personConverter = Format(personReads, Json.writes[Person])
}
The key thing to note here is surrounding the whole thing in a JsResult courtesy of the for-comprehension and the yield. This gives us all the necessary checking (like the name field being there and being a String, and the hobbies field being there).
The code within the yield block only runs if we've got something that looks pretty close to a Person. Then we can safely try validating the hobbies as a List[Hobby], and convert the result to an Option[List[Hobby]]. It'll be a None if it didn't work (thus it must have been a string) and so we default it to the empty list as required.
Thanks #millhouse answer, it definitely works. Like he said we need a custom Reads[Person] to properly convert it.
I also post my code as reference.
implicit val personJsonReads: Reads[Person] = (
(__ \ "name").read[String] and
(__ \ "hobbies").read[List[Hobby]].orElse(Reads.pure(List()))
) (Person.apply _)
read[List[Hobby]].orElse(Reads.pure(List())) will generate the empty list when the value cannot convert to List[Hobby].

Play Scala JSON - conditionally add field to JSON object in Writes

In our app, we have pretty complex structure of objects that is getting converted to JSON and back. Until now most of the formatted are symmetrical (except some very specific cases, and even these for security reasons).
Now we are facing a more complex case where conversion of an object into JSON (writes) needs to create an additional field at a time of conversion while the case class does not have that field.
For example, here is one of our existing formatters:
case class ChecklistColumn(kind: ColumnKind.Value, descriptor: Descriptor.Value, data: JsValue) extends Column
implicit val checklistResultChecklistDataFormat: Format[ChecklistColumn] = (
(__ \ "kind").format[ColumnKind.Value] and
(__ \ "descriptor").format[Descriptor.Value] and
(__ \ "data").format[JsValue]
)(ChecklistColumn.apply, unlift(ChecklistColumn.unapply))
This one creates a json that will looks like:
{
"kind": <String>,
"descriptor": <String>,
"data": <JsValue>
}
What we need to achieve is:
{
"kind": <String>,
"descriptor": <String>,
"data": <JsValue>,
"normalized_data": <JsString>
}
But, only in case when the data is type of JsString (in any other case normalized_data can be left empty, byt ideally should not even exist).
I do understand that we have to create separate Reads & Writes for that.
But, I am not sure, how to implement the logic that will react differently to a different type of data.
Of course, there is always an option to create fully custom writes:
override def writes(column: ChecklistColumn): JsValue = {...}
But, this will create a huge complexity in a code that will be hard to maintain.
What is the cleanest way to implement something like that?
Have a look at ScalaJsonTransformers. You can create a transformer that creates the normalised field from a string data value and use it to, erm, transform your original Format to a new Writes. Here's a slightly simplified example that could doubtless be improved (you'll want to check various edge cases):
case class ChecklistColumn(kind: String, descriptor: String, data: JsValue)
// The original format.
val checklistFormat: Format[ChecklistColumn] = (
(__ \ "kind").format[String] and
(__ \ "descriptor").format[String] and
(__ \ "data").format[JsValue]
)(ChecklistColumn.apply, unlift(ChecklistColumn.unapply))
// A transformer that looks for a "data" field with a string
// value and adds the normalized_data field if it finds one.
val checklistTransformer: Reads[JsObject] = JsPath.json.update(
(__ \ "data").read[String].flatMap (
str => (__ \ "normalized_data").json.put(JsString(str + "!!!"))))
// A new derived Writes which writes the transformed value if
// the transformer succeeds (a data string), otherwise the
// original value.
implicit val checklistWrites: Writes[ChecklistColumn] = checklistFormat
.transform (js => js.transform(checklistTransformer).getOrElse(js))
That gives me:
Json.prettyPrint(Json.toJson(ChecklistColumn("a", "b", JsNumber(1))))
// {
// "kind" : "a",
// "descriptor" : "b",
// "data" : 1
// }
Json.prettyPrint(Json.toJson(ChecklistColumn("a", "b", JsString("c"))))
// {
// "kind" : "a",
// "descriptor" : "b",
// "data" : "c",
// "normalized_data" : "c!!!"
// }

Play ScalaJSON Reads[T] parsing

I am writing an Json parse for an rest webservice response, I have an Json file looking as:
{"program": {
"name": "myname",
"#id": "12345",
"$": "text text text"
}, etc. etc.
I wrote an case class for the Reads object:
case class program(name:String)
implicit val programFormat = Json.format[program]
And this pseudo code for get a data:
val x=(jobj \ "program").validate[program]
x match {
case JsSuccess(pr, _) => println("JsSuccess:"+pr)
for(p<- pr.program)
{
println(p.name)
}
case error: JsError => ....
}
For the field name no problem, the code work well, but I don't understand how capture the field "#id" and the field "$" because I cannot create an param in case class named: #id or $.
Thank you for your help.
More correct solution in my opinion is creating own Reads, that is:
case class Program(name: String, id: String, dollar: String)
implicit val programWrites: Reads[Program] = (
(__ \ "name").read[String] ~
(__ \ "#id").read[String] ~
(__ \ "$").read[String]
)(Program.apply _)
Docs: https://www.playframework.com/documentation/2.4.x/ScalaJsonCombinators#Reads
Another solution, i think much worse one, is using backtick sign
case class Program(name: String, `#id`: String, `$`: String)
implicit val programFormat = Json.format[Program]
It allows to write special signs in method names, field names and so on.
More about it: Need clarification on Scala literal identifiers (backticks)

Why is Angular JSON coming in as a ListBuffer? (And how to parse...)

Edit I initially described this wrong -- rewritten below.
For some reason, I cannot get Angular to send the JSON payload in a way that it is interpreted as a simple string. Looking at the JsObject body, it is always a ListBuffer for some unknown reason.
Code
Application.scala
(Taking from here)
implicit val userReads: Reads[User] = (
(JsPath \ "name").read[String] and
(JsPath \ "language").read[Int]
)((name: String, languageId: Int) => User(None, name, languageId))
def submitUser = Action(BodyParsers.parse.json) {
implicit request =>
val userResult = request.body.validate[User]
userResult.fold(
errors => {
BadRequest(Json.obj("status" -> "KO", "message" -> JsError.toFlatJson(errors)))
},
user => {
userDao.add(user)
Ok(Json.obj("status" -> "OK", "message" -> ("User '" + user.full_name + "' saved.")))
}
)
}
User.scala
case class User(id: Option[Int], full_name: String, language: Int)
app.js
(Only relevant piece)
$scope.submit = function(event) {
$scope.loadingTracker = promiseTracker();
return $http.post('/user', {
data: {name: 'Joe Blow', language: 1}
}, { tracker: $scope.loadingTracker })
.success(function(id) {
console.log("Success! Created user with id " + id);
});
}
(I have tried using JSON.stringify, etc.)
Exception
Doing the traditional "Reads" on this gives me this error, or something similar:
play.api.Application$$anon$1: Execution exception[[JsResultException: JsResultException(errors:List((,List(ValidationError(error.expected.jsstring,WrappedArray())))))]]
at play.api.Application$class.handleError(Application.scala:293) ~[play_2.10-2.2.2.jar:2.2.2]
at play.api.DefaultApplication.handleError(Application.scala:399) [play_2.10-2.2.2.jar:2.2.2]
at play.core.server.netty.PlayDefaultUpstreamHandler$$anonfun$2$$anonfun$applyOrElse$3.apply(PlayDefaultUpstreamHandler.scala:261) [play_2.10-2.2.2.jar:2.2.2]
at play.core.server.netty.PlayDefaultUpstreamHandler$$anonfun$2$$anonfun$applyOrElse$3.apply(PlayDefaultUpstreamHandler.scala:261) [play_2.10-2.2.2.jar:2.2.2]
at scala.Option.map(Option.scala:145) [scala-library-2.10.4.jar:na]
at play.core.server.netty.PlayDefaultUpstreamHandler$$anonfun$2.applyOrElse(PlayDefaultUpstreamHandler.scala:261) [play_2.10-2.2.2.jar:2.2.2]
Caused by: play.api.libs.json.JsResultException: JsResultException(errors:List((,List(ValidationError(error.expected.jsstring,WrappedArray())))))
at play.api.libs.json.JsValue$$anonfun$2.apply(JsValue.scala:64) ~[play-json_2.10-2.2.2.jar:2.2.2]
at play.api.libs.json.JsValue$$anonfun$2.apply(JsValue.scala:64) ~[play-json_2.10-2.2.2.jar:2.2.2]
at play.api.libs.json.JsResult$class.fold(JsResult.scala:71) ~[play-json_2.10-2.2.2.jar:2.2.2]
at play.api.libs.json.JsError.fold(JsResult.scala:10) ~[play-json_2.10-2.2.2.jar:2.2.2]
at play.api.libs.json.JsValue$class.as(JsValue.scala:62) ~[play-json_2.10-2.2.2.jar:2.2.2]
at play.api.libs.json.JsObject.as(JsValue.scala:164) ~[play-json_2.10-2.2.2.jar:2.2.2]
Because the data looks like this:
> body = {play.api.libs.json.JsObject#14793} {"data":{"name":"Joe Blow","language":1}} and "JsObject" has a member fields which is a ListBuffer (in this case, size = 1)
I know this is a totally "noob" question but I don't know how to read the ListBuffer from the body, let alone parse it into the case class. I would like to just parse this whatever way is most proper; it makes no sense to me why this simple JSON is being turned into a ListBuffer by Play.
Any suggestions/ideas?
I'm not sure your searching for data in the right place.
(JsPath \ "name")
lets me think that you're skipping the "data" level in your JSON data structure. You may try to add
(JsPath \ "data" \ "name")
and see if things get better. Moreover, you may try to define your Reads directly defining a implicit format this way :
Json.format[User]
and then read your data as you're already doing :
val placeResult = request.body.validate[User]
These are just some hints, I'm not a JSON nor a scala expert and I haven't tried the code...

Discard wrapper data when using Reads[T]

I think what I'm trying to do should be relatively simple, but I'm not certain. I'm making an API call to get a list of applications and I'd like to read them out. However, the list is nested into the response. The response looks something like:
{
"response": {
"instances": [
{ /* object I'm concerned with reading */ },
...
]
}
}
I have a reader currently defined as:
implicit val appReader : Reads[App] = (
(__ \ "ip_address").read[String] and
(__ \ "hostname").read[String] and
(__ \ "application_version").read[String]
)(App)
And a class of:
case class App(
ip: String,
hostname: String,
version: String
)
However, I'm not sure of how to get at the data since it is buried in the response or if there is a way that I can discard that data.
I assume that the applications lie within the instances array like this:
val js = Json.parse("""
{
"response": {
"instances": [
{ "ip_address": "192.168.1.1", "hostname": "host1", "application_version": "1.0"},
{ "ip_address": "192.168.1.2", "hostname": "host2", "application_version": "1.0"}
]
}
}
""")
Now, to read the list of applications defined as App:
val responseRead =
(__ \ "response").read(
(__ \ "instances").read[List[App]])
val apps = responseRead.reads(js).get
This gives you the list of applications leaving out the rest. If the data structure is somewhat different than assumed, this should point you in the right direction.
Edit: Most of the time I prefer to use the Json macro inception of play. The code for your example could be written as follows:
case class App(
ip_address: String,
hostname: String,
application_version: String)
object App {
implicit val appFormat = Json.format[App]
}
case class Response(
instances: List[App])
object Response {
implicit val responseFormat = Json.format[Response]
}
case class Root(
response: Response)
object Root {
implicit val rootFormat = Json.format[Root]
}
val root = js.as[Root]
val apps = root.response.instances
As you can see, the case class vals must have the same name as their Json counter parts. Additionally, I used format instead of reads which is of course up to you, but most of the time you need both reads and writes which is what format gives you.