提问者:小点点

没有play. api.libs.json.Format实例可用于模型。隐式范围内的Account tStatus


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

这是从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个答案

匿名用户

因此,需要将此代码添加到对象Account tStatus代码块中,因为我们需要使用fromIntInt转换为Account tStatus。这是为Account tStatus定义的Reads

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

什么是Reads?它只是一个trait,它定义了JsValue(封装JSON值的play类)应该如何从JSON反序列化为某种类型。该trait只需要实现一个方法,一个读取方法,该方法接受一些json并返回某种类型的JsResult。所以你可以在上面的代码中看到,我们有一个Reads,它将在JSON中寻找一个名为的字段作为,并尝试将其读取为整数。从那里,它将使用已经定义的fromInt方法将其转换为Account tStatus。例如,在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的另一部分是Writes。毫不奇怪,这个特性与读取相似,只是它的作用相反。您正在使用您的类Account tStatus并创建一个JsValue(JSON)。因此,您只需实现写入方法。

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]正在做我们刚刚为您做的所有工作!但是对于Account。它可以做到这一点,因为它是一个案例类,只有不到22个字段。此外,Account的每个字段都有一种与JSON转换的方法(通过ReadsWrites)。您的错误消息显示帐户无法自动为其创建格式,因为它的一部分(状态字段)没有格式化程序。

现在,你为什么要这样做呢?因为Account tStatus不是案例类,所以你不能在上面调用Json. format[Account tStatus]。而且因为它的子类是每个对象,它们没有为它们定义取消应用方法,因为它们不是案例类。所以你必须向库解释如何序列化和反序列化。

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

奖金回合

所以,你可能真的不想自己做这项工作,有一种方法可以避免不得不这样做,这样你就可以做Json. format[Account tStatus]。你可以看到Json.format使用应用取消应用方法来完成它的肮脏工作。scala,这两个方法是为案例类自动定义的。但是你没有理由不能自己定义它们并免费获得它们给你的一切!

那么,应用取消应用看起来像类型签名吗?它会根据类而变化,但在这种情况下应用应该匹配Int=

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

而unapplication与此相反,但它需要返回一个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.我今天要旅行,但如果其中一些没有意义,请随时留下任何评论,我稍后会尽力回复您