【问题标题】:How to solve this Scala/Play compilation error (wrong type being returned)?如何解决这个 Scala/Play 编译错误(返回错误的类型)?
【发布时间】:2014-07-13 04:01:26
【问题描述】:

我正在尝试编写一个插入 Play Framework 控制器的 JSON 反序列化器,以代替标准的 Play JSON 库。理由是能够直接使用杰克逊。感谢a recipe by Maarten Winkels,我已经能够想出一个可插入的反序列化器,但是由于我不理解的编译错误而被卡住了(免责声明:我是 Scala 新手)。

编译错误源于JsonObjectParser.apply 的一个分支显然试图返回Object 的一个实例,而它应该是Result。我不明白为什么会这样。我的问题是,我该如何解决这个错误?

编译错误

编译错误如下:

/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

这是有问题的源代码:

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

或者,

如果你们知道在 Jackson 之上将自定义 JSON 反序列化器插入 Play 的更好方法,那也是可以接受的。毕竟,这就是我要在这里做的事情。

【问题讨论】:

  • case Left((r, in)) => Done(Left(r), El(in)) 没有任何意义 - 如果有任何剩余的输入,则紧接上面的 case Left(b) 分支将被遵循。我想你可能想要的是case Left(r) => Done(Left(r), Empty)。但是,我认为如果您使用transform 而不是apply,您可以完全取消第二个flatMap
  • @wingedsubmariner 想尝试用完整的代码输入答案吗?我可以用手握住。

标签: json scala playframework compiler-errors playframework-2.3


【解决方案1】:

eofOrElseIteratee 将前一个Iteratee 的结果包装成Either。因为前一个 Iteratee 的结果已经是 Either,所以你最终会得到类似 Either[Result, Either[Result, A]] 的结果。调用joinRight 可以将其转换为我们需要的Either[Result, A]_.global.onBadRequest(request, "Invalid Json") 也返回 Future[SimpleResult],而不是 SimpleResult - 我已经删除了该代码。

下面我已经应用了这些修复,并简化了从 .left.map 调用返回的元组,并且还使用了 transform 而不是 apply 以取消最后一个 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)
  }
}

【讨论】:

  • 这失败并出现另一个错误:JsonObjectParser.scala:25: type mismatch; [error] found : play.api.libs.iteratee.Iteratee[Array[Byte],Either[play.api.mvc.Results.Status,Serializable with Product with scala.util.Either[Object,A]]] [error] required: play.api.libs.iteratee.Iteratee[Array[Byte],Either[play.api.mvc.Result,A]] [error] }.flatMap(Iteratee.eofOrElse(Results.EntityTooLarge))
  • 当前以JsonObjectParser.scala:25: Cannot prove that Serializable with Product with scala.util.Either[Object,A] <:< scala.util.Either[play.api.mvc.Results.Status,C]. [error] }.flatMap(Iteratee.eofOrElse(Results.EntityTooLarge)).map(_.joinRight) 失败
【解决方案2】:

这一行:

case Left(b) => Done(Left(b), Empty)

正在使用Left(b) 调用Done,其中b 的类型为play.api.mvc.Results.Status。这设置了将由封闭的flatMap 返回的迭代对象类型的期望。

在这一行:

case Left((r, in)) => Done(Left(r), El(in))

DoneLeft(r) 调用,其中 r 的类型为 Object,导致返回的迭代器类型与 case Left 分支不同。

将该行更改为:

case Left((r: Result, in)) => Done(Left(r), El(in))

产生与前一个相同类型的迭代器,避免编译错误。

如果不深入研究算法,就无法判断这是否是适当的更改,但更一般的答案是所有代码分支都必须返回兼容的类型。

作为提示,使用 Eclipse 的 Scala 插件,您可以将鼠标悬停在变量上以查看其类型。有时,将代码分成块分配给显式类型变量也可以更清楚地处理哪些类型,但代价是使代码更冗长。

【讨论】:

  • 谢谢,但在进行更改后,它会因另一个错误而中断:method apply has incompatible type [error] def apply(request: RequestHeader): Iteratee[Array[Byte], Either[Result, A]] = {
  • 这是一个额外的错误。带有Play.maybeApplication.map 的语句返回Product with Serializable with scala.util.Either[(Object, Array[Byte]),A] 而不仅仅是Either[(Object, Array[Byte]),A]。还有更多错误如match may not be exhaustive。 Stack Overflow 不适合自定义代码调试,但按照我的回答中解释的过程应该可以让你做到这一点。
猜你喜欢
  • 1970-01-01
  • 2018-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-15
  • 1970-01-01
相关资源
最近更新 更多