0

我正在使用使用 Scala 2.10.2(运行 Java 1.7.0_45)构建的 play 2.2.0。我的应用程序中的大多数路由/端点接收一个 json 请求,使用表单将数据解析为案例类,然后对接收到的数据执行业务逻辑。我已经抽象了这一点。它有效,但我不喜欢的一件事是我将案例类实例转换为 java.lang.Object。然后,特定的处理函数必须将案例类实例重新转换回其原始类型。

我很新玩/ scala。scala中是否有一种更简洁的方法来更改我的处理程序函数上的签名,这样就不需要强制转换了。必须施放然后重新施放感觉是错误的。也许这只是这种情况下抽象的代价(不是双关语)?

下面的代码有:

  1. 调用管道方法的 RefreshTokenController。注意不必要的演员表。
  2. 执行不必要强制转换的 ControllerUtil.handleJsonRequest 方法。

我认为我正在寻找的是正确使用 scala 泛型。这不会编译,但类似于:

    def handleJsonRequest(form: Form[T], handlerFunction: (T) => SimpleResult, request: Request[Object]): SimpleResult {

在下面的代码中查找“TODO”注释。如果您有一种更好、更“类似游戏”的方式来抽象解析和业务逻辑,则可以加分。例如,我应该有自己的应用程序特定的操作吗?

{"paramA": "paramA_Value", "paramB": "paramB_Value"}


object RefreshTokenController extends Controller {

  private case class RequestData(paramA: String, paramB: String)

  private val requestForm = Form(
    mapping(
      "paramA" -> nonEmptyText,
      "ParamB" -> nonEmptyText
    )(RequestData.apply)(RequestData.unapply)
  )

  def myEndPoint = Action(ControllerUtil.formOrJsonParser) {
    request =>  {
      val response = ControllerUtil.handleJsonRequest(requestForm, requestHandlerFunction, request)
      response
    }
  }

  val requestHandlerFunction: (Object) => SimpleResult = processRequest

  def processRequest(refreshDataObj: Object) : SimpleResult = {
    //TODO: Yuck, how can I get rid of the unnecessary cast
    val refreshData: RequestData = refreshDataObj.asInstanceOf[RequestData]

    //Business logic removed since it's not relevant
  }
}


object ControllerUtil {

  /**
   * Handles the plumbing of parsing a json request and applying business logic.
   */
  def handleAsyncJsonRequest(form: Form[_],
    handlerFunction: (Object) => Future[SimpleResult],
    request: Request[Object]): Future[SimpleResult] = {
    // Convert the request body to JSON even if using URL-Form-Encoding
    val formData: Object = parseUsingForm(form, request)
    handlerFunction(formData)
  }

  /**
   * Handles the plumbing of parsing a json request and applying business logic.
   *
   */
  def handleJsonRequest(form: Form[_],
    handlerFunction: (Object) => SimpleResult,
    request: Request[Object]): SimpleResult = {
    //TODO: How can I change the function signature so I can avoid casting/recasting.
    val formData: Object = parseUsingForm(form, request)
    handlerFunction(formData)
  }

  def parseUsingForm(form: Form[_], request: Request[Object]): Object = {
    val jsonData: JsValue = parseRequestToJson(request)
    parseUsingForm(form, jsonData)
  }

  def parseUsingForm(form: Form[_], jsData: JsValue): Object = {
    form.bind(jsData).fold(
      formWithErrors => {
        val error = Results.BadRequest(formWithErrors.errorsAsJson)
        throw new UserErrorException(error)
      },
      parsedData => {
        //TODO: Yuck, I don't like having to cast here.
        val formData: Object = parsedData.asInstanceOf[Object]
        formData
      }
    )
  }

  def parseRequestToJson(request: Request[Object]): JsValue = {
    var jsonLoginRequest: JsValue = null
    request.body match {
      case json: JsObject => jsonLoginRequest = json
      case form: Map[String, Seq[String]] => jsonLoginRequest = Json.toJson(form.map {
        case (k, v) => (k, v.head)
      })
    }

    jsonLoginRequest
  }

  /**
   *  parse html encoded form containing json data or String json data
   */
  val formOrJsonParser = parse.using {
    request =>
      request.contentType.map(_.toLowerCase(Locale.ENGLISH)) match {
        case Some("application/json") | Some("text/json") => parse.json
        case Some("application/x-www-form-urlencoded") | Some("text/x-www-form-urlencoded") =>
          parse.urlFormEncoded
        case _ =>
          parse.error(Future.successful(Results.UnsupportedMediaType("Invalid content type specified")))
      }
  }
}
4

1 回答 1

1

关于为什么需要强制转换的简短答案是,您并没有真正使用 Scala 类型系统,而是Object到处传递,因为您的表单是作为通配符类型给出的Form[_]。表单必须使用为其定义映射的对象类型进行参数化 - 如果您想以通用方式使用它们,请使用如下所示:

/** 
 * Given a form and a handler, produce a result.
 */
def parseForm[T](form: Form[T], handler: T => SimpleResult)(implicit request: Request[AnyContent]): SimpleResult = {
  form.bindFromRequest.fold(
    errForm => BadRequest(errForm.errorsAsJson),
    item => handler(item)
  )
}

但是表单实际上是用于 formUrlEncoded 数据的,并且您正在接收 JSON,因此它们不是这项工作的错误工具。

我认为有一种更简单的方法可以做你想做的事,假设(基于你的代码):

  • 你总是得到 JSON 数据
  • 内容类型可能并不总是 JSON

在这种情况下,您可以使用BodyParsers.tolerantJson解析器,它会给您一个JsValue反序列化但不会检查内容类型的麻烦。

您可以使用Reads[T]. 这样的事情应该让你开始:

package controllers

import play.api.mvc._
import play.api.libs.json.{Reads, JsError, Json}


object RefreshTokenController extends Controller {

  case class RequestData(paramA: String, paramB: String)

  object RequestData {
    implicit val reads: Reads[RequestData] = Json.reads[RequestData]
  }

  def myEndPoint = Action(BodyParsers.parse.tolerantJson) { request =>
    processJson(request.body, processItem)
  }

  def processJson[T](json: JsValue, handler: T => SimpleResult)(implicit rd: Reads[T]): SimpleResult = {
    json.validate[T].fold(
      err => BadRequest(JsError.toFlatJson(err)),
      item => handler(item)
    )
  }

  def processItem(item: RequestData) : SimpleResult = {
    Ok(s"Fancy business logic with $item")
  }
}

定义了一个读取类型以将implicit val reads: Reads[RequestData] = Json.reads[RequestData]JSON 解析/验证到您的数据案例类中。这implicit意味着,因为它在您的类的伴随对象中,所以无论何时您尝试验证此类型的对象时都会自动找到它,但也可以显式指定它。如果验证失败,您将收到描述错误(例如缺少路径paramA)的错误,我们在BadRequest.

当我们验证传入的 JSON 时,我们使用一个fold接受参数的函数:一个处理错误的函数和一个处理正确结果的函数。

一些额外的 Scala 建议:

  • 使用AnyRef而不是 Java 的Object,但这样做通常表明您正在以某种方式颠覆类型系统。
  • 永远不要使用null,除非您正在与 Java API 交互并且绝对无法避免它。而是Option[String]在某些东西可以合法地“不存在”时使用。
于 2013-11-10T19:12:32.137 回答