没有play.api.libs.json.Format的实例可用于隐式范围内的models.AccountStatus

时间:2017-06-30 14:59:35

标签: json scala playframework

没有play.api.libs.json.Format的实例可用于隐式范围内的models.AccountStatus。

这是从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.")
  }
}

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

1 个答案:

答案 0 :(得分:2)

因此,需要在object AccountStatus代码块的内添加此代码,因为我们需要使用fromIntInt转换为AccountStatus 1}}。这是为AccountStatus定义的Reads

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

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

这个读者并不完美,主要是因为它没有处理你的代码会给你带来超出数字的错误:

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

您可以通过使Reads处理错误来解决此问题。我可以告诉你如果你想要,但首先Format的另一部分是WritesThis trait,不出所料,与读取相似,只是反过来。您正在上课AccountStatus并创建JsValue(JSON)。因此,您只需要实现writes方法。

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

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

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

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

现在,为什么必须这样做?由于AccountStatus不是案例类,因此您无法在其上调用Json.format[AccountStatus]。并且因为它的子类是每个对象,它们没有为它们定义unapply方法,因为它们不是case类。因此,您必须向库解释如何序列化和反序列化。

既然你说你是scala的新手,我想隐含的概念仍然有些陌生。我建议你玩它/做一些阅读,以便在看到编译器抱怨无法找到它需要的隐含时,掌握该怎么做。

奖金回合

所以,你可能真的不想自己做这项工作,有一种方法可以避免这样做,所以你可以做Json.format[AccountStatus]。您看到Json.format使用applyunapply方法执行其脏工作。在scala中,这两种方法是为case类自动定义的。但是没有理由你不能自己定义它们并获得它们免费提供给你的一切!

那么,applyunapply看起来是什么样的签名呢?它每个类都会更改,但在这种情况下apply应匹配Int => AccountStatus(从int到AccountStatus的函数)。所以它定义如下:

def apply(i: Int): AccountStatus = fromInt(i)

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

def unapply(as: AccountStatus): Option[Int] = Option(as.as)

定义了这两个,你不需要自己定义读写,而只需调用

// this is still inside the AccountStatus object { ... } 
implicit val asFormat = Json.format[AccountStatus]

它将以类似的方式工作。

.P.S。我今天正在旅行,但如果其中一些没有意义,请随时留下任何评论,我会稍后再回复你