隐式作用域中的 models.AccountStatus 没有可用的 play.api.libs.json.Format 实例

Rav*_*yal 3 json scala playframework

隐式作用域中的 models.AccountStatus 没有可用的 play.api.libs.json.Format 实例。

这是取自 github 页面的代码,仅更改了类名和变量名。

package models

import slick.jdbc.H2Profile._
import play.api.libs.json._

case class Account(id: Long, name: String, category: Int, status:AccountStatus)

object Account {
  implicit val accountFormat = Json.format[Account]
}

sealed abstract class AccountStatus(val as:Int)

object AccountStatus{
  final case object Draft extends AccountStatus(0)
  final case object Active extends AccountStatus(1)
  final case object Blocked extends AccountStatus(2)
  final case object Defaulter extends AccountStatus(3)

  implicit val columnType: BaseColumnType[AccountStatus] = MappedColumnType.base[AccountStatus,Int](AccountStatus.toInt, AccountStatus.fromInt)

  private def toInt(as:AccountStatus):Int = as match {
    case Draft => 0
    case Active => 1
    case Blocked => 2
    case Defaulter => 3
  }

  private def fromInt(as: Int): AccountStatus = as match {
    case 0 => Draft
    case 1 => Active
    case 2 => Blocked
    case 3 => Defaulter
    _ => sys.error("Out of bound AccountStatus Value.")
  }
}
Run Code Online (Sandbox Code Playgroud)

https://github.com/playframework/play-scala-slick-example/blob/2.6.x/app/models/Person.scala

Edg*_*erg 7

因此,需要将此代码添加到代码块内部object AccountStatus,因为我们需要使用fromInt将 an 转换Int为 an AccountStatus。这是Reads为 AccountStatus 定义的:

implicit object AccountStatusReads extends Reads[AccountStatus] {
  def reads(jsValue: JsValue): JsResult[AccountStatus] = {
   (jsValue \ "as").validate[Int].map(fromInt)
  }
}
Run Code Online (Sandbox Code Playgroud)

什么是Reads?它只是trait定义了如何将 JsValue(封装 JSON 值的播放类)从 JSON 反序列化为某种类型。该特征只需要实现一个方法,该reads方法接受一些 json 并返回JsResult某种类型的 a。因此,您可以在上面的代码中看到,我们有一个Reads将在 JSON 中查找名为的字段as,并尝试将其作为整数读取。然后,它会将其转换为AccountStatus使用已定义的 fromInt 方法。例如,在 scala 控制台中,您可以执行以下操作:

import play.api.libs.json._ 
// import wherever account status is and the above reader
scala> Json.parse("""{"as":1}""").as[AccountStatus]
res0: AccountStatus = Active
Run Code Online (Sandbox Code Playgroud)

不过,这个阅读器并不完美,主要是因为它没有处理代码在超出范围的数字时给您带来的错误:

scala> Json.parse("""{"as":20}""").as[AccountStatus]
java.lang.RuntimeException: Out of bound AccountStatus Value.
  at scala.sys.package$.error(package.scala:27)
  at AccountStatus$.fromInt(<console>:42)
  at AccountStatusReads$$anonfun$reads$1.apply(<console>:27)
  at AccountStatusReads$$anonfun$reads$1.apply(<console>:27)
  at play.api.libs.json.JsResult$class.map(JsResult.scala:81)
  at play.api.libs.json.JsSuccess.map(JsResult.scala:9)
  at AccountStatusReads$.reads(<console>:27)
  at play.api.libs.json.JsValue$class.as(JsValue.scala:65)
  at play.api.libs.json.JsObject.as(JsValue.scala:166)
  ... 42 elided
Run Code Online (Sandbox Code Playgroud)

您可以通过处理Reads错误来处理这个问题。如果您愿意,我可以向您展示如何操作,但首先 a 的另一部分Format是 a Writes不出所料,这个特性与读取类似,只是它的作用相反。您正在上课AccountStatus并创建一个JsValue(JSON)。因此,您只需实现该writes方法即可。

implicit object AccountStatusWrites extends Writes[AccountStatus] {
  def writes(as: AccountStatus): JsValue = {
    JsObject(Seq("as" -> JsNumber(as.as)))
  }
}
Run Code Online (Sandbox Code Playgroud)

然后可以使用该类将该类序列化为 JSON,如下所示:

scala> Json.toJson(Draft)
res4: play.api.libs.json.JsValue = {"as":0}
Run Code Online (Sandbox Code Playgroud)

现在,这实际上足以让您的错误消失。为什么?因为Json.format[Account]我们刚刚为您完成了所有工作!但对于帐户。它可以做到这一点,因为它是一个案例类并且具有少于 22 个字段。此外,每个字段Account都有一种与 JSON 相互转换的方法(通过 aReadsWrites)。您的错误消息显示帐户无法自动为其创建格式,因为其中一部分(状态字段)没有格式化程序。

现在,你为什么必须这样做?因为AccountStatus不是案例类,所以不能调用Json.format[AccountStatus]它。因为它的子类都是对象,没有unapply为它们定义方法,因为它们不是案例类。所以你必须向库解释如何序列化和反序列化。

既然你说你是scala的新手,我想隐式的概念仍然有点陌生。我建议您尝试一下/做一些阅读,以了解当您看到编译器抱怨无法找到它需要的隐式内容时该怎么做。

奖金轮

因此,您可能真的不想自己做这项工作,并且有一种方法可以避免必须这样做,这样您就可以做Json.format[AccountStatus]。您会看到Json.format使用applyunapply方法来完成其肮脏的工作。在scala中,这两个方法是为案例类自动定义的。但您没有理由不能自己定义它们并免费获得它们为您提供的一切!

那么,applyunapply看起来像类型签名吗?它会根据类而变化,但在本例中apply应该匹配Int => AccountStatus(从 int 到 AccountStatus 的函数)。所以它的定义如下:

def apply(i: Int): AccountStatus = fromInt(i)
Run Code Online (Sandbox Code Playgroud)

和 unapply 与此相反类似,但它需要返回一个Option[Int],所以它看起来像

def unapply(as: AccountStatus): Option[Int] = Option(as.as)
Run Code Online (Sandbox Code Playgroud)

通过这两个定义,您不需要自己定义读取和写入,而只需调用

// this is still inside the AccountStatus object { ... } 
implicit val asFormat = Json.format[AccountStatus]
Run Code Online (Sandbox Code Playgroud)

它将以类似的方式工作。

.PS 我今天要去旅行,但如果其中一些内容没有意义,请随时留下任何评论,我稍后会尽力回复您