简体   繁体   中英

How to solve this Scala/Play compilation error (wrong type being returned)?

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?

Compilation 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))

JsonObjectParser.scala

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)
      }
    }
  }
}

Alternatively,

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.

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)
  }
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM