Reputation: 68127
I am trying to write a JSON deserializer that plugs into Play Framework controllers, in lieu of the standard Play JSON library. The rationale is to be able to use Jackson directly. I have been able to come up with a pluggable deserializer, thanks to a recipe by Maarten Winkels, but I am stuck due to a compilation error that I just don't understand (disclaimer: I am a Scala novice).
The compilation error stems from the fact that apparently a branch of JsonObjectParser.apply
tries to return an instance of Object
, whereas it should be Result
. I don't understand why this is happening though. My question is, how do I solve this error?
The compilation error looks like this:
/Users/arve/Projects/test/JsonObjectParser.scala:26: type mismatch;
[error] found : Object
[error] required: play.api.mvc.Result
[error] case Left((r, in)) => Done(Left(r), El(in))
This is the source code in question:
import java.io.{ByteArrayInputStream, InputStream}
import play.api.Play
import play.api.libs.iteratee.Input._
import play.api.libs.iteratee._
import play.api.mvc._
import scala.concurrent.ExecutionContext.Implicits.global
class JsonObjectParser[A: Manifest](deserializer: (InputStream) => A) extends BodyParser[A] {
val JsonMaxLength = 4096
def apply(request: RequestHeader): Iteratee[Array[Byte], Either[Result, A]] = {
Traversable.takeUpTo[Array[Byte]](JsonMaxLength).apply(Iteratee.consume[Array[Byte]]().map { bytes =>
scala.util.control.Exception.allCatch[A].either {
deserializer(new ByteArrayInputStream(bytes))
}.left.map { e =>
(Play.maybeApplication.map(_.global.onBadRequest(request, "Invalid Json")).getOrElse(
Results.BadRequest), bytes)
}
}).flatMap(Iteratee.eofOrElse(Results.EntityTooLarge))
.flatMap {
case Left(b) => Done(Left(b), Empty)
case Right(it) => it.flatMap {
// Won't compile
case Left((r, in)) => Done(Left(r), El(in))
case Right(a) => Done(Right(a), Empty)
}
}
}
}
If you guys know of a better way to plug a custom JSON deserializer into Play, on top of Jackson, that would be acceptable too. That is what I'm trying to do here after all.
Upvotes: 1
Views: 873
Reputation: 13667
The eofOrElse
Iteratee
wraps the result of the previous Iteratee
into an Either
. Because the result of the previous Iteratee was already an Either
, you end up with something like Either[Result, Either[Result, A]]
. A call to joinRight
can transform this into the Either[Result, A]
we require. Also _.global.onBadRequest(request, "Invalid Json")
returns a Future[SimpleResult]
, not a SimpleResult
- I've removed that code.
Below I've applied those fixes as well as simplified the tuple returned from .left.map
call and also used transform
instead of apply
in order to do away with the last flatMap
.
class JsonObjectParser[A: Manifest](deserializer: (InputStream) => A) extends BodyParser[A] {
val JsonMaxLength = 4096
def apply(request: RequestHeader): Iteratee[Array[Byte], Either[SimpleResult, A]] = {
Traversable.takeUpTo[Array[Byte]](JsonMaxLength).transform {
Iteratee.consume[Array[Byte]]().map { bytes =>
scala.util.control.Exception.allCatch[A].either {
deserializer(new ByteArrayInputStream(bytes))
}.left.map { _ =>
Results.BadRequest
}
}
}.flatMap(Iteratee.eofOrElse(Results.EntityTooLarge)).map(_.joinRight)
}
}
Upvotes: 2
Reputation: 22395
This line:
case Left(b) => Done(Left(b), Empty)
Is calling Done
with Left(b)
where b
is of type play.api.mvc.Results.Status
. This sets the expectation for the type of the iteratee that will be returned by the enclosing flatMap
.
On this line:
case Left((r, in)) => Done(Left(r), El(in))
Done
is being called with Left(r)
where r
is of type Object
, resulting in an iteratee of a different type than the case Left
branch is returning.
Changing that line to:
case Left((r: Result, in)) => Done(Left(r), El(in))
produces an iteratee of the same type as the previous, avoiding the compile error.
Without digging deep in the algorithm, it can't be told if that is the appropriate change, but the more general answer is that all code branches must return compatible types.
As a tip, using the Scala plugin for Eclipse you can hover the mouse over a variable to see its type. Sometimes, dividing the code in chunks assigned to explicitly typed variables can also make it clearer which types are being dealth with, at the expense of making the code more verbose.
Upvotes: 0