如何从隐式请求中解析来自 JSON 的有界类型 T 的对象?
How can I parse an object of a bounded type T from JSON from an implicit request?
我有一些简单的消息,它们的伴随对象中定义了隐式 Json.reads
和 Json.formats
。所有这些消息都扩展 MyBaseMessage
。
换句话说,对于任何 T <: MyBaseMessage
,T
是可(反)序列化的。
这些消息表示要在集群上执行的简单 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.
但是,所有这些消息都是可序列化的,并且为它们定义了 Reads
和 Format
。
我尝试将 (implicit fjs: Reads[T])
传递给 parseAndForward
,希望隐式提供所需的 Reads
(尽管它应该已经隐式提供),但它没有帮助。
我该如何解决这个问题?
JsValue#as[A]
需要隐式 Reads[A]
才能将 JSON 反序列化为某种类型 A
。也就是说,您收到的错误消息是因为编译器无法保证任何类型 T <: MyBaseMessage
都存在 Reads[T]
。假设 sendToCluster
以相同的方式参数化,这可以很容易地通过在每个方法调用中要求隐式 Reads[T]
来解决。听起来你已经很接近了,只需要通过要求 FooAction
的 Reads[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)
}
)
}
我有一些简单的消息,它们的伴随对象中定义了隐式 Json.reads
和 Json.formats
。所有这些消息都扩展 MyBaseMessage
。
换句话说,对于任何 T <: MyBaseMessage
,T
是可(反)序列化的。
这些消息表示要在集群上执行的简单 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 implicitReads
orFormat
for this type.
但是,所有这些消息都是可序列化的,并且为它们定义了 Reads
和 Format
。
我尝试将 (implicit fjs: Reads[T])
传递给 parseAndForward
,希望隐式提供所需的 Reads
(尽管它应该已经隐式提供),但它没有帮助。
我该如何解决这个问题?
JsValue#as[A]
需要隐式 Reads[A]
才能将 JSON 反序列化为某种类型 A
。也就是说,您收到的错误消息是因为编译器无法保证任何类型 T <: MyBaseMessage
都存在 Reads[T]
。假设 sendToCluster
以相同的方式参数化,这可以很容易地通过在每个方法调用中要求隐式 Reads[T]
来解决。听起来你已经很接近了,只需要通过要求 FooAction
的 Reads[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)
}
)
}