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

How can I parse an object of a bounded type T from JSON from an implicit request?

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

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

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

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

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

但是我发现如下错误:

No Json deserializer found for type T. Try to implement an implicit Reads or Format for this type.

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

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

我该如何解决这个问题?

JsValue#as[A] 需要隐式 Reads[A] 才能将 JSON 反序列化为某种类型 A。也就是说,您收到的错误消息是因为编译器无法保证任何类型 T <: MyBaseMessage 都存在 Reads[T]。假设 sendToCluster 以相同的方式参数化,这可以很容易地通过在每个方法调用中要求隐式 Reads[T] 来解决。听起来你已经很接近了,只需要通过要求 FooActionReads[T] 来更进一步(因为该调用是确定类型的地方)。

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。这将 return 一个 BadRequest 如果收到的甚至不是 JSON。其次,as如果接收到的JSON不能反序列化为预期的类型会抛出异常,你可以使用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)
    }
  )
}