I have some simple messages with implicit Json.reads
and Json.formats
defined in their companion objects. All of these messages extend MyBaseMessage
.
In other words, for any T <: MyBaseMessage
, T
is (de)serializable.
These messages represent simple CRUD operations to be performed on a cluster, so there's an Play server that will sit between a CLI sending JSON and the Cluster. Because the operations are simple, I should be able to make some very generic Action
s on the Play side: when I receive JSON at an endpoint, deserialize the message according to the endpoint and forward that message to the cluster.
My ultimate goal is to do something like this:
// AddBooMessage extends MyBaseMessage
def addBoo = FooAction[AddBooMessage]
// AddMooMessage extends MyBaseMessage
def addMoo = FooAction[AddMooMessage]
// etc. ...
So when a request is sent to the route corresponding to the addBoo
message, the request's JSON will be parsed into an AddBooMessage
message and pushed to the cluster. Repeat ad nauseam.
I have the following written:
private def FooAction[T <: MyBaseMessage] = Action {
implicit request =>
parseAndForward[T](request)
}
private def parseAndForward[T <: MyBaseMessage](request: Request[AnyContent]) = {
val parsedRequest = Json.parse(request.body.toString).as[T]
Logger.info(s"Got '$parsedRequest' request. Forwarding it to the Cluster.")
sendToCluster(parsedRequest)
}
But I find the following error:
No Json deserializer found for type
T
. Try to implement an implicitReads
orFormat
for this type.
However, all of these messages are serializable and have both Reads
and Format
defined for them.
I tried passing (implicit fjs: Reads[T])
to parseAndForward
in hopes to implicitly provide the Reads
required (though it should already be implicitly provided), but it didn't help.
How can I solve this problem?
JsValue#as[A]
needs an implicit Reads[A]
in order to deserialize JSON to some type A
. That is, the error message you're getting is caused because the compiler can't guarantee there is a Reads[T]
for any type T <: MyBaseMessage
. Assuming sendToCluster
is parameterized the same way, this can easily by fixed by simply requiring an implicit Reads[T]
in each method call. It sounds like you were close, and just needed to take things a step further by requiring the Reads[T]
from FooAction
, as well (since that call is where the type is determined).
private def FooAction[T <: MyBaseMessage : Reads] = Action { implicit request =>
parseAndForward[T](request)
}
private def parseAndForward[T <: MyBaseMessage : Reads](request: Request[AnyContent]) = {
val parsedRequest = Json.parse(request.body.toString).as[T]
Logger.info(s"Got '$parsedRequest' request. Forwarding it to the Cluster.")
sendToCluster(parsedRequest) // Assuming this returns a `Future[Result]`
}
If your intention if you use the above code by manually supplying the type parameter, this will work just fine.
There are some other improvements I think you can make here. First, if you always expect JSON, you should require the parse.json
BodyParser
. This will return a BadRequest
if what is received isn't even JSON. Second, as
will throw an exception if the received JSON cannot be deserialized into the expected type, you can use JsValue#validate
to do this more safely and fold
the result to handle the success and error cases explicitly. For example:
private def FooAction[T <: MyBaseMessage] = Action.async(parse.json) { implicit request =>
parseAndForward[T](request)
}
private def parseAndForward[T <: MyBaseMessage](request: Request[JsValue]) = {
request.body.validate[T].fold(
error => {
Logger.error(s"Error parsing request: $request")
Future.successful(BadRequest)
},
parsed => {
Logger.info(s"Got '$parsed' request. Forwarding it to the Cluster.")
sendToCluster(parsed)
}
)
}