我正在使用使用Scala 2.10.2(运行Java 1.7.0_45)构建的play 2.2.0。我的应用程序中的大多数路由/端点都接收到json请求,解析使用表单将数据解析为case类,然后对接收到的数据执行业务逻辑。我已经抽象了这个。它有效,但我不喜欢的一件事是我将case类实例转换为java.lang.Object。然后,特定处理程序函数必须将case类实例重新转换回其原始类型。
我很喜欢玩/ scala。 scala中是否有更简洁的方法来更改处理程序函数上的签名,以便不需要进行转换。不得不施放然后重新投掷是不对的。也许这只是这种情况下抽象的代价(没有双关语意)?
以下代码包含:
我认为我正在寻找的是正确使用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")))
}
}
}
答案 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,因此它们是这项工作的错误工具。
我认为(根据您的代码)假设有一种更简单的方法来做您想做的事情:
在这种情况下,您可以使用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]
。