Scala Play中的空序列化(隐式写入)

问题描述 投票:0回答:1

我正在尝试使用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](成为唯一的实数变量类型),并得到了一个新的错误。

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

NOTE我想我有问题。由于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%确信我的推理,如果我错了,请纠正我:)

json scala serialization playframework implicit
1个回答
1
投票

为了更好地说明,ServiceResponse案例类采用类型参数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是String且是标准类型,所以默认情况下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>
© www.soinside.com 2019 - 2024. All rights reserved.