游戏案例类的通用类型

时间:2013-11-10 16:34:43

标签: json forms scala playframework-2.2

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

我很喜欢玩/ scala。 scala中是否有更简洁的方法来更改处理程序函数上的签名,以便不需要进行转换。不得不施放然后重新投掷是不对的。也许这只是这种情况下抽象的代价(没有双关语意)?

以下代码包含:

  1. 调用管道方法的RefreshTokenController。注意不必要的演员。
  2. 执行不必要演员的ControllerUtil.handleJsonRequest方法。
  3. 我认为我正在寻找的是正确使用scala泛型。这不会编译,但类似于:

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

    在下面的代码中查找“TODO”评论。如果你有一个更好的,更像“游戏”的方式来抽象解析和业务逻辑,那么奖励点。例如,我应该具有自己的应用程序特定的Action吗?

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

1 个答案:

答案 0 :(得分: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]验证和解析传入的JSON。这样的事情应该让你开始:

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]