如何从隐式请求中解析JSON中有界类型T的对象?

时间:2017-02-27 16:29:27

标签: json scala serialization play-json

我有一些简单的消息,其伴随对象中定义了隐式Json.readsJson.formats。所有这些消息都延伸MyBaseMessage

换句话说,对于任何T <: MyBaseMessageT是(de)可序列化的。

这些消息表示要在群集上执行的简单CRUD操作,因此Play服务器将位于发送JSON和群集的CLI之间。因为操作很简单,所以我应该能够在Play端创建一些非常通用的Action:当我在端点接收JSON时,根据端点反序列化消息并将该消息转发给集群。 / p>

我的最终目标是做这样的事情:

// AddBooMessage extends MyBaseMessage
def addBoo = FooAction[AddBooMessage]  

// AddMooMessage extends MyBaseMessage
def addMoo = FooAction[AddMooMessage]

// etc. ...

因此,当请求被发送到与addBoo消息相对应的路由时,请求的JSON将被解析为AddBooMessage消息并被推送到集群。重复广告恶心。

我写了以下内容:

  private def FooAction[T <: MyBaseMessage] = Action {
    implicit request =>
       parseAndForward[T](request)
  } 

  private def parseAndForward[T <: MyBaseMessage](request: Request[AnyContent]) = {
    val parsedRequest = Json.parse(request.body.toString).as[T]
    Logger.info(s"Got '$parsedRequest' request. Forwarding it to the Cluster.")
    sendToCluster(parsedRequest)
  }

但我发现以下错误:

  

找不到类型T的Json反序列化程序。尝试为此类型实现隐式ReadsFormat

但是,所有这些消息都是可序列化的,并且为它们定义了ReadsFormat

我尝试将(implicit fjs: Reads[T])传递给parseAndForward,希望隐含地提供所需的Reads(虽然它应该已经隐式提供),但它没有帮助。

我该如何解决这个问题?

1 个答案:

答案 0 :(得分:2)

JsValue#as[A]需要隐式Reads[A]才能将JSON反序列化为某种类型A。也就是说,导致的错误消息是由于编译器无法保证任何类型Reads[T]都有T <: MyBaseMessage。假设sendToCluster以相同的方式进行参数化,可以通过在每个方法调用中简单地要求隐式Reads[T]来轻松修复。听起来你很接近,只需要从Reads[T] FooAction开始要求更进一步(因为该调用是确定类型的地方)。

private def FooAction[T <: MyBaseMessage : Reads] = Action { implicit request =>
  parseAndForward[T](request)
} 

private def parseAndForward[T <: MyBaseMessage : Reads](request: Request[AnyContent]) = {
  val parsedRequest = Json.parse(request.body.toString).as[T]
  Logger.info(s"Got '$parsedRequest' request. Forwarding it to the Cluster.")
  sendToCluster(parsedRequest) // Assuming this returns a `Future[Result]`
}

如果您打算通过手动提供类型参数来使用上述代码,那么这样就可以了。

我认为你可以在这里做一些其他的改进。首先,如果您总是期望JSON,则应该要求parse.json BodyParser。如果收到的内容甚至不是JSON,这将返回BadRequest。其次,如果收到的JSON无法反序列化为预期类型,as将抛出异常,您可以使用JsValue#validate更安全地执行此操作,并fold结果来处理成功和错误案件明确。例如:

private def FooAction[T <: MyBaseMessage] = Action.async(parse.json) { implicit request =>
  parseAndForward[T](request)
} 

private def parseAndForward[T <: MyBaseMessage](request: Request[JsValue]) = {
  request.body.validate[T].fold(
    error => {
      Logger.error(s"Error parsing request: $request")
      Future.successful(BadRequest)
    },
    parsed => {
      Logger.info(s"Got '$parsed' request. Forwarding it to the Cluster.")
      sendToCluster(parsed)
    }
  )
}