本文介绍了在隐式作用域中没有可用于 models.AccountStatus 的 play.api.libs.json.Format 实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

No instance of play.api.libs.json.Format is available for models.AccountStatus in the implicit scope.

这是摘自github页面的代码,只更改了类名和变量名.

This is the code taken from a github page, and only class names and variable names are changed.

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

推荐答案

所以,这段代码需要添加在 object AccountStatus 代码块的inside,因为我们需要使用 fromIntInt 转换为 AccountStatus.这是为 AccountStatus 定义的 Reads:

So, this code needs to be added inside of the object AccountStatus code block since we need to use fromInt to transform an Int to an AccountStatus. This is a Reads defined for AccountStatus:

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

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

What's a Reads? It's just a trait that defines how a JsValue (the play class encapsulating JSON values) should be deserialized from JSON to some type. The trait only requires one method to be implemented, a reads method which takes in some json and returns a JsResult of some type. So you can see in the above code that we have a Reads that will look for a field in JSON called as and try to read it as an integer. From there, it will then transform it into an AccountStatus using the already defined fromInt method. So for example in the scala console you could do this:

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

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

This reader isn't perfect though, mainly because it's not handling the error your code will give you on out of bound numbers:

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.这个特性,不出所料,它与 read 相似,只是相反.您正在使用您的课程 AccountStatus 并创建一个 JsValue (JSON).所以,你只需要实现 writes 方法.

You could handle this by making the Reads handle the error. I can show you how if you want, but first the other part of a Format is a Writes. This trait, unsurprisingly is similar to reads except it does the reverse. You're taking your class AccountStatus and creating a JsValue (JSON). So, you just have to implement the writes method.

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

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

Then this can be used to serialize that class to JSON like so:

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

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

Now, this is actually enough to get your error to go away. Why? Because Json.format[Account] is doing all the work we just did for you! But for Account. It can do this because it's a case class and has less than 22 fields. Also every field for Account has a way to be converted to and from JSON (via a Reads and Writes). Your error message was showing that Account could not have a format automatically created for it because part of it (status field) had no formatter.

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

Now, why do you have to do this? Because AccountStatus is not a case class, so you can't call Json.format[AccountStatus] on it. And because the subclasses of it are each objects, which have no unapply method defined for them since they're not case classes. So you have to explain to the library how to serialize and deserialize.

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

Since you said you're new to scala, I imagine that the concept of an implicit is still somewhat foreign. I recommend you play around with it / do some reading to get a grasp of what to do when you see that the compiler is complaining about not being able to find an implicit it needs.

奖金回合

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

So, you might really not want to do that work yourself, and there is a way to avoid having to do it so you can do Json.format[AccountStatus]. You see Json.format uses the apply and unapply methods to do its dirty work. In scala, these two methods are defined automatically for case classes. But there's no reason you can't define them yourself and get everything they give you for free!

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

So, what do apply and unapply look like type signature wise? It changes per class, but in this case apply should match Int => AccountStatus (a function that goes from an int to an AccountStatus). So it's defined like so:

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

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

and unapply is similar to the reverse of this, but it needs to return an Option[Int], so it looks like

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

定义了这两个之后,您就不需要自己定义读取和写入,而只需调用

with both of these defined you don't need to define the reads and writes yourself and instead can just call

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

它会以类似的方式工作.

and it will work in a similar fashion.

.P.S.我今天要出差,但如果其中一些没有意义,请随时留下任何评论,我稍后会尽量回复您

这篇关于在隐式作用域中没有可用于 models.AccountStatus 的 play.api.libs.json.Format 实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-30 14:47