Scala Play 中的空序列化(隐式写入)
Null serialization in Scala Play (implicit Writes)
我正在试验 Scala Play,不明白为什么 Null json 序列化不能开箱即用。我写了一个简单的 class 来封装响应 (ServiceResponse) 中的数据,其中 return 一个参数化的可空字段 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% 肯定我的推理,如果我错了请纠正我:)
为了更好地解释,ServiceResponse 案例 class 采用类型参数 T,它可以是任何东西。 play-json 只能为标准 scala 类型提供 JSON 格式,对于自定义类型,您需要定义 JSON 格式化程序。
import play.api.libs.json._
case class ServiceResponse[T](
data: T,
errorMessage: Option[String],
debugMessage: Option[String]
)
def format[T](implicit format: Format[T]) = Json.format[ServiceResponse[T]]
implicit val formatString = format[String]
val serviceResponseStr = ServiceResponse[String]("Hello", None, None)
val serviceResponseJsValue = Json.toJson(serviceResponseStr)
val fromString =
Json.fromJson[ServiceResponse[String]](serviceResponseJsValue)
serviceResponseJsValue
fromString
serviceResponseJsValue.toString
Json.parse(serviceResponseJsValue.toString).as[ServiceResponse[String]]
在上面的示例中,您可以看到我想创建一个数据为字符串的 ServiceResponse,因此我实现了 Json.toJson 以及 Json.fromJson 所必需的格式字符串为类型 T 实现读者和作者。由于 T 是字符串并且是标准类型,play-json 默认情况下解析相同。
我添加了 scastie 片段,这将帮助您更好地理解它,并且您可以尝试使用它。
<script src="https://scastie.scala-lang.org/shankarshastri/spqJ1FQLS7ym1vm1ugDFEA/8.js"></script>
上面的解释足以满足一个用例,其中在 None 的情况下,密钥甚至不会作为 json 的一部分出现,但问题显然需要 key: null
,以防找不到数据。
import play.api.libs.json._
implicit val config =
JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
case class ServiceResponse[T](
data: Option[T],
errorMessage: Option[String],
debugMessage: Option[String]
)
def format[T](implicit format: Format[T]) = Json.format[ServiceResponse[T]]
implicit val formatString = format[String]
val serviceResponseStr = ServiceResponse[String](None, None, None)
val serviceResponseJsValue = Json.toJson(serviceResponseStr)
val fromString =
Json.fromJson[ServiceResponse[String]](serviceResponseJsValue)
serviceResponseJsValue
fromString
serviceResponseJsValue.toString
Json.parse(serviceResponseJsValue.toString).as[ServiceResponse[String]]
在范围内引入以下行,将确保为可选写入空值。
implicit val config =
JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
<script src="https://scastie.scala-lang.org/shankarshastri/rCUmEqXLTeuGqRNG6PPLpQ/6.js"></script>
我正在试验 Scala Play,不明白为什么 Null json 序列化不能开箱即用。我写了一个简单的 class 来封装响应 (ServiceResponse) 中的数据,其中 return 一个参数化的可空字段 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% 肯定我的推理,如果我错了请纠正我:)
为了更好地解释,ServiceResponse 案例 class 采用类型参数 T,它可以是任何东西。 play-json 只能为标准 scala 类型提供 JSON 格式,对于自定义类型,您需要定义 JSON 格式化程序。
import play.api.libs.json._
case class ServiceResponse[T](
data: T,
errorMessage: Option[String],
debugMessage: Option[String]
)
def format[T](implicit format: Format[T]) = Json.format[ServiceResponse[T]]
implicit val formatString = format[String]
val serviceResponseStr = ServiceResponse[String]("Hello", None, None)
val serviceResponseJsValue = Json.toJson(serviceResponseStr)
val fromString =
Json.fromJson[ServiceResponse[String]](serviceResponseJsValue)
serviceResponseJsValue
fromString
serviceResponseJsValue.toString
Json.parse(serviceResponseJsValue.toString).as[ServiceResponse[String]]
在上面的示例中,您可以看到我想创建一个数据为字符串的 ServiceResponse,因此我实现了 Json.toJson 以及 Json.fromJson 所必需的格式字符串为类型 T 实现读者和作者。由于 T 是字符串并且是标准类型,play-json 默认情况下解析相同。
我添加了 scastie 片段,这将帮助您更好地理解它,并且您可以尝试使用它。
<script src="https://scastie.scala-lang.org/shankarshastri/spqJ1FQLS7ym1vm1ugDFEA/8.js"></script>
上面的解释足以满足一个用例,其中在 None 的情况下,密钥甚至不会作为 json 的一部分出现,但问题显然需要 key: null
,以防找不到数据。
import play.api.libs.json._
implicit val config =
JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
case class ServiceResponse[T](
data: Option[T],
errorMessage: Option[String],
debugMessage: Option[String]
)
def format[T](implicit format: Format[T]) = Json.format[ServiceResponse[T]]
implicit val formatString = format[String]
val serviceResponseStr = ServiceResponse[String](None, None, None)
val serviceResponseJsValue = Json.toJson(serviceResponseStr)
val fromString =
Json.fromJson[ServiceResponse[String]](serviceResponseJsValue)
serviceResponseJsValue
fromString
serviceResponseJsValue.toString
Json.parse(serviceResponseJsValue.toString).as[ServiceResponse[String]]
在范围内引入以下行,将确保为可选写入空值。
implicit val config =
JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
<script src="https://scastie.scala-lang.org/shankarshastri/rCUmEqXLTeuGqRNG6PPLpQ/6.js"></script>