隐式作用域中没有可用于model.AccountStatus的play.api.libs.json.Format实例. [英] No instance of play.api.libs.json.Format is available for models.AccountStatus in the implicit scope

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

问题描述

在隐式范围内,model.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

推荐答案

因此,由于需要使用fromInt进行转换,因此需要在object AccountStatus代码块的内部中添加此代码IntAccountStatus.这是为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. 此特征 ,毫不奇怪,它与读取类似,但它的操作相反.您正在上课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的每个字段都有一种方法(通过ReadsWrites)与JSON相互转换.您的错误消息显示帐户无法为其自动创建格式,因为该帐户的一部分(状态字段)没有格式器.

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不是案例类,所以您不能在其上调用Json.format[AccountStatus].并且因为它的子类是每个对象,所以没有为它们定义unapply方法,因为它们不是case类.因此,您必须向库解释如何序列化和反序列化.

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)

和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.我今天正在旅行,但是如果其中一些内容没有任何意义,请随时发表评论,我会在稍后尝试与您联系

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

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆