Play Framework WebSocket ArrayIndexOutOfBounds Error - json

I'm having a WebSocket endpoint exposed by the Play Framework like this:
def socket: WebSocket = WebSocket.acceptOrResult[JsValue, JsValue] { request =>
Future.successful(
if (acceptedSubProtocol(request.headers, appConfig.ocppServerCfg.supportedProtocols)) { // TODO: Get the Protocol via AppConfig
Right(ActorFlow.actorRef { actorRef =>
Props(new ProvisioningActor(actorRef))
})
} else {
logger.warn(s"Supported Protocol is not one of ${appConfig.ocppServerCfg.supportedProtocols.mkString(",")} " +
"in the Sec-WebSocket-Protocol header")
Left(Forbidden)
}
)
}
The following implicit conversion for the incoming Json which looks like this:
implicit val ocppCallRequestReads2: Reads[OCPPCallRequest] = Reads { jsValue =>
val messageTypeIdE = (jsValue \ 0).toEither
val messageIdE = (jsValue \ 1).toEither
val actionNameE = (jsValue \ 2).toEither
val payloadE = (jsValue \ 3).toEither
val yielded = for {
messageTypeId <- messageTypeIdE
messageId <- messageIdE
actionName <- actionNameE
payload <- payloadE
} yield {
OCPPCallRequest( // Here I know all 4 exists, so safe to call head
messageTypeId.head.as[Int],
messageId.head.as[String],
actionName.head.as[String],
payload
)
}
yielded match {
case Right(ocppCallRequest) => JsSuccess(ocppCallRequest)
case Left(errors) =>
println("****************+ ERRORS")
errors.messages.foreach(println)
println("****************+ ERRORS")
JsError(errors)
}
}
And the actual JSON:
[
"19223201",
"BootNotification",
{
"reason": "PowerUp",
"chargingStation": {
"serialNumber" : "12345",
"model" : "",
"vendorName" : "",
"firmwareVersion" : "",
"modem": {
"iccid": "",
"imsi": ""
}
}
}
]
What I'm trying to do is to validate the incoming JSON and propogate any errors to the client. When I tried to run my example., I'm not able to pass the error back as a JSON response, but the WebSocket endpoint closes with an internal server error as seen below:
[info] a.a.CoordinatedShutdown - Running CoordinatedShutdown with reason [ApplicationStoppedReason]
[info] a.e.s.Slf4jLogger - Slf4jLogger started
[info] play.api.Play - Application started (Dev) (no global state)
****************+ ERRORS
Array index out of bounds in ["19223201","BootNotification",{"reason":"PowerUp","chargingStation":{"serialNumber":"12345","model":"","vendorName":"","firmwareVersion":"","modem":{"iccid":"","imsi":""}}}]
****************+ ERRORS
[error] p.c.s.c.WebSocketFlowHandler - WebSocket flow threw exception
java.lang.ClassCastException: scala.Tuple2 cannot be cast to play.api.libs.json.JsValue
at akka.stream.impl.fusing.Map$$anon$1.onPush(Ops.scala:52)
at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:542)
at akka.stream.impl.fusing.GraphInterpreter.processEvent(GraphInterpreter.scala:496)
at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:390)
at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:650)
at akka.stream.impl.fusing.ActorGraphInterpreter$SimpleBoundaryEvent.execute(ActorGraphInterpreter.scala:61)
at akka.stream.impl.fusing.ActorGraphInterpreter$SimpleBoundaryEvent.execute$(ActorGraphInterpreter.scala:57)
at akka.stream.impl.fusing.ActorGraphInterpreter$BatchingActorInputBoundary$OnNext.execute(ActorGraphInterpreter.scala:104)
at akka.stream.impl.fusing.GraphInterpreterShell.processEvent(ActorGraphInterpreter.scala:625)
at akka.stream.impl.fusing.ActorGraphInterpreter.akka$stream$impl$fusing$ActorGraphInterpreter$$processEvent(ActorGraphInterpreter.scala:800)
Any clues as to how I can control the WebSocket endpoint not to crash into an exception? What happens is that the connection gets closed and I do not want that.

Related

How to convert Json objects into arrays [(string, double)] in Scala JSON4s?

I don't know how to use CustomSerializer to convert JSON to Scala data
I don't need to serialize the parts, but deserialization is difficult
Can someone help me? Thank you very much
{
"parameters": {
"field_owner": true,
"sample_field": "gender",
"sample_strategy": "by_ratio",
"sample_config": [["male", 1500.0], ["female", 1500.0]],
"with_replacement": true,
"random_seed": 114514,
"least_sample_num": 100
},
"input_artifacts": {"dataset": {"object_id": "upload/ml_classifier_predict_result"}},
"output_artifacts": {"predict_result": {"object_id": "ingest_output.csv"}}
}
class TupleSerializer extends CustomSerializer[(String,Double)](tuple_formats => (
{
case JArray(List(JString(x), JDouble(y))) =>
(x,y)
},{
case x:Tuple2[String,Double] => null
}
))
implicit val formats: Formats = DefaultFormats + new TupleSerializer
val fileBuffer = Source.fromFile(path)
val jsonString = fileBuffer.mkString
parse(jsonString).extract[StratifiedSampleArgument]
}
error message:
o usable value for parameters
No usable value for sample_config
Expected object with 1 element but got JArray(List(JString(male), JDouble(1500.0)))
at com.alipay.morse.sgxengine.driver.StratifiedSampleDriverTest.main(StratifiedSampleDriverTest.scala:15)
Caused by: org.json4s.package$MappingException:
No usable value for sample_config
Expected object with 1 element but got JArray(List(JString(male), JDouble(1500.0)))
at com.alipay.morse.sgxengine.driver.StratifiedSampleDriverTest.main(StratifiedSampleDriverTest.scala:15)
Caused by: org.json4s.package$MappingException: Expected object with 1 element but got JArray(List(JString(male), JDouble(1500.0)))
at com.alipay.morse.sgxengine.driver.StratifiedSampleDriverTest.main(StratifiedSampleDriverTest.scala:15)
[INFO]
[INFO] Results:
[INFO]
[ERROR] Errors:
[ERROR] StratifiedSampleDriverTest.main:15 » Mapping No usable value for parameters
No...
[INFO]
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0

How to handle error in JSON data has incorrect is node

I'm expecting following json format:
'{
"teamId" : 9,
"teamMembers" : [ {
"userId" : 1000
}, {
"userId" : 2000
}]
}'
If I test my code with following format:-
'{
"teaXmId" : 9,
"teamMembers" : [ {
"usXerId" : 1000
}, {
"userXId" : 2000
}]
}'
I'm parsing json value as follows:-
val userId = (request.body \\ "userId" )
val teamId = (request.body \ "teamId")
val list = userId.toList
list.foreach( x => Logger.info("x val: "+x)
It doesn't throw any error to handle. Code execution goes one. Later if I try to use teamId or userId, of course it doesn't work then.
So how to check whether parsing was done correctly or stop execution right away and notify user to provide correct json format
If a value is not found when using \, then the result will be of type JsUndefined(msg). You can throw an error immediately by making sure you have the type you expect:
val teamId = (request.body \ "teamId").as[Int]
or:
val JsNumber(teamId) = request.body \ "teamId" //teamId will be BigDecimal
When using \\, if nothing is found, then an empty List is returned, which makes sense. If you want to throw an error when a certain key is not found on any object of an array, you might get the object that contains the list and proceed from there:
val teamMembers = (request.body \"teamMembers").as[Seq[JsValue]]
or:
val JsObject(teamMembers) = request.body \ "teamMembers"
And then:
val userIds = teamMembers.map(v => (v \ "userId").as[Int])

JSON List to Scala List

I am creating a backend API with Play Framework and Scala. I would like to map the incoming request to a scala object. One of the instance variables of the object is a list of channels. Here is what I currently have:
Controller method that takes the request and attempts to map it to a user:
def addUser = Action(parse.json) { request =>
request.body.validate[User].fold({ errors =>
BadRequest(Json.obj(
"status" -> "Error",
"message" -> "Bad JSON",
"details" -> JsError.toFlatJson(errors)
))
}, { user =>
User.create(user.pushToken, user.channels)
Ok(Json.obj("status" -> "OK", "message" -> "User created"))
})
}
User case class:
case class User(id: Pk[Long], pushToken: String, channels: List[String])
User formatter:
implicit val userFormat = (
(__ \ "id").formatNullable[Long] and
(__ \ "pushToken").format[String] and
(__ \ "channels").format[List[String]]
)((id, pushToken, channels) => User(id.map(Id(_)).getOrElse(NotAssigned), pushToken, channels),
(u: User) => (u.id.toOption, u.pushToken, u.channels))
User anorm create method:
def create(pushToken: String, channels: List[String]) {
DB.withConnection { implicit c =>
SQL("insert into user (pushToken, channels) values ({pushToken}, {channels})").on(
'pushToken -> pushToken,
'channels -> channels
).executeUpdate()
}
}
When I try to compile, I get:
Compilation error[could not find implicit value for parameter extractor: anorm.Column[List[String]]]
Ideally, I would like to be able to accept this as a user:
{
"pushToken":"4jkf-fdsja93-fjdska34",
"channels": [
"channelA", "channelB", "channelC"
]
}
and create a user from it.
You can't use List[String] as column value in Anorm, thats the problem
You should use mkString method or smth else

Scala Object From JSON Request

I have a controller method in my Scala Play project that takes JSON as input. I would like to turn this JSON into a model object I have.
Here is my controller method:
def broadcastPost = Action(parse.json) { request =>
(request.body).asOpt[Post].map { post =>
Post.create(post.channelId, post.message, post.datePosted, post.author)
Ok(play.api.libs.json.Json.toJson(
Map("status" -> "OK", "message" -> ("Post created"))
))
}.getOrElse {
BadRequest(play.api.libs.json.Json.toJson(
Map("status" -> "Error", "message" -> ("Missing parameter [Post]"))
))
}
}
And here is the model:
case class Post(id: Pk[Long], channelId: Long, message: String, datePosted: Date, author: String)
and its implicit formatter:
implicit val postFormat = (
(__ \ "id").formatNullable[Long] and
(__ \ "channelId").format[Long] and
(__ \ "message").format[String] and
(__ \ "datePosted").format[Date] and
(__ \ "author").format[String]
)((id, channelId, message, datePosted, author) => Post(id.map(Id(_)).getOrElse(NotAssigned), channelId, message, datePosted, author),
(p: Post) => (p.id.toOption, p.channelId, p.message, p.datePosted, p.author))
When I send a POST request to that method with the following data:
{"channelId":1, "message":"Wanna get a game in?", "dateCreated":"5-15-2013", "author":"Eliot Fowler"}
I get the following response:
{"status":"Error","message":"Missing parameter [Post]"}
I am very new to Scala, so I may be overlooking something very simple here.
Instead of using asOpt, which loses the error, you should use validate, which will allow you to return the error message, and then see what the problem is, eg:
def broadcastPost = Action(parse.json) { request =>
request.body.validate[Post].fold({ errors =>
BadRequest(Json.obj(
"status" -> "Error",
"message" -> "Bad JSON",
"details" -> JsError.toFlatJson(errors)
))
}, { post =>
Post.create(post.channelId, post.message, post.datePosted, post.author)
Ok(Json.obj("status" -> "OK", "message" -> "Post created"))
})
}
Now, what I'm guessing that will tell you is that "5-15-2013" is not a valid date. The default date format for JSON in Play is yyyy-MM-dd. You can specify a custom one by modifying your format to say:
...
(__ \ "datePosted").format[Date](Format(Reads.dateReads("MM-dd-yyyy"), Writes.dateWrites("MM-dd-yyyy"))) and
...
Another built in one Reads.IsoDateReads, this is more standard than the American only month day year format, another approach that avoids this issue altogether is use a Long as milliseconds since epoch.

Akka :: dispatcher [%name%] not configured, using default-dispatcher

I created the followind application.conf:
akka {
actor {
prio-dispatcher {
type = "Dispatcher"
mailbox-type = "my.package.PrioritizedMailbox"
}
}
}
when dumping configuration with
actorSystem = ActorSystem.create()
println(actorSystem.settings)
I'm getting the output:
# application.conf: 5
"prio-dispatcher" : {
# application.conf: 7
"mailbox-type" : "my.package.PrioritizedMailbox",
# application.conf: 6
"type" : "Dispatcher"
},
and later on
[WARN] [08/30/2012 22:44:54.362] [default-akka.actor.default-dispatcher-3] [Dispatchers] Dispatcher [prio-dispatcher] not configured, using default-dispatcher
What am I missing here?
UPD Found the solution here, had to use the name "akka.actor.prio-dispatcher"
The configuration above dictates that name of mailbox is akka.actor.prio-dispatcher
Description of the problem: http://groups.google.com/group/akka-user/browse_thread/thread/678f2ae1c068e0fa