【发布时间】:2020-09-27 07:43:16
【问题描述】:
我正在尝试使用 Scala Play,但不明白为什么 Null json 序列化不能开箱即用。我编写了一个简单的类来封装响应中的数据(ServiceResponse),它返回一个参数化的可为空字段data,并且还隐含了一个 Writes[Null],我错过了什么?编译器建议编写一个可以工作的 Writes[ServiceResponse[Null]],但感觉很麻烦,我想知道是否有更简洁的方法来解决问题。下面是代码和错误。
// <!-- EDIT -->
// The data I want to transfer
case class Person (name: String, age: Int)
object Person {
def apply(name: String, age: Int): Person = new Person(name, age)
implicit val reader: Reads[Person] = Json.reads[Person]
implicit val writer: OWrites[Person] = Json.writes[Person]
}
// <!-- END EDIT -->
case class ServiceResponse[T] (data: T, errorMessage: String, debugMessage: String)
object ServiceResponse {
def apply[T](data: T, errorMessage: String, debugMessage: String): ServiceResponse[T] =
new ServiceResponse(data, errorMessage, debugMessage)
implicit def NullWriter: Writes[Null] = (_: Null) => JsNull
implicit def writer[T](implicit fmt: Writes[T]) = Json.writes[ServiceResponse[T]]
}
// <!-- EDIT -->
// Given the above code I would expect
// the call ` Json.toJson(ServiceResponse(null, "Error in deserializing json", null))`
// to produce the following Json: `{ "data": null, "errorMessage": "Error in deserializing json", "debugMessage": null }`
No Json serializer found for type models.ServiceResponse[Null]. Try to implement an implicit Writes or Format for this type.
In C:\...\EchoController.scala:19
15 val wrapAndEcho = Action(parse.json) {
16 request =>
17 request.body.validate[Person] match {
18 case JsSuccess(p, _) => Ok(Json.toJson(echoService.wrapEcho(p)))
19 case JsError(errors) => BadRequest(Json.toJson(ServiceResponse(null,
20 "Error in deserializing json", null)))
21 }
22 }
编辑
尝试使用 Option 代替(确实是更类似于 Scala 的解决方案),但代码的结构是相同的并报告相同的错误
object ServiceResponse {
def apply[T](data: Option[T], errorMessage: Option[String], debugMessage: Option[String]): ServiceResponse[T] =
new ServiceResponse(data, errorMessage, debugMessage)
implicit def optionWriter[T](implicit fmt: Writes[T]) = new Writes[Option[T]] {
override def writes(o: Option[T]): JsValue = o match {
case Some(value) => fmt.writes(value)
case None => JsNull
}
}
implicit def writer[T](implicit fmt: Writes[Option[T]]) = Json.writes[ServiceResponse[T]]
}
我认为我需要一些结构上的改变,但我不知道是什么。还尝试将 fmt 的类型更改为 Writes[T] (作为 T 唯一的真实变量类型)并得到一个新的错误。
diverging implicit expansion for type play.api.libs.json.Writes[T1]
[error] starting with object StringWrites in trait DefaultWrites
[error] case JsError(errors) => BadRequest(Json.toJson(ServiceResponse(None,
...
以为我找到了一个合理的解决方案,但在隐式扩展时仍然出错:(。我真的需要一个提示。
object ServiceResponse {
def apply[T](data: Option[T], errorMessage: Option[String], debugMessage: Option[String]): ServiceResponse[T] =
new ServiceResponse(data, errorMessage, debugMessage)
implicit def writer[T](implicit fmt: Writes[T]): OWrites[ServiceResponse[T]] = (o: ServiceResponse[T]) => JsObject(
Seq(
("data", o.data.map(fmt.writes).getOrElse(JsNull)),
("errorMessage", o.errorMessage.map(JsString).getOrElse(JsNull)),
("debugMessage", o.debugMessage.map(JsString).getOrElse(JsNull))
)
)
}
注意
我想我有问题。由于 null 是 Person 实例的一个可能值,我希望它由 Writes[Person] 处理。事实上,Writes 在其类型参数中被定义为不变(它是 trait Writes[A] 而不是 trait Writes[+A])所以 Writes[Null] 与隐式参数的定义不匹配,如果它被定义为 Writes[+A](在违反 Liskow 替换原则是错误的;它可能是Writes[-A],但这也不能解决问题,因为我们正在尝试使用 Person 的子类型 Null)。总结:没有比编写Writes[ServiceResponse[Null]] 的特定实现更短的方法来处理具有空数据字段的ServiceResponse,它既不是Write[ServiceResponse[Person]] 的超类型也不是子类型。 (一种方法可能是联合类型,但我认为这是一种矫枉过正)。 90% 肯定我的推理,如果我错了,请纠正我:)
【问题讨论】:
-
你为什么要使用
Null它几乎没有任何意义 -
使用 Option[T] 作为 JSON 模型的一部分构建它。它可以缓解事情。它会看起来很整洁。
-
@cchantep Null 是 null 文字的类型。在我看来听起来不错。
-
Null是一种解决方法类型,而不是一种健全的类型,特别是在使用类型类时
标签: json scala serialization playframework implicit