【问题标题】:Play how to implement an implicit Writes or Format for a case class including an Enumeration播放如何为包含枚举的案例类实现隐式写入或格式
【发布时间】:2019-12-11 08:37:42
【问题描述】:

以下this answer 展示了如何使用案例类将枚举绑定到表单。

但在 Play 2.7.3 中,此代码失败:

No Json serializer found for type jura.SearchRequest. Try to implement an implicit Writes or Format for this type.

当我实现格式化程序时:

object SearchRequest {
  implicit val searchRequestFormat: OFormat[SearchRequest] = Json.format[SearchRequest]
}

我明白了

没有 play.api.libs.json.Format 的实例可用于 scala.Enumeration.Value 在隐式范围内

我应该尝试为系统 scala.Enumeration 类型编写格式化程序吗?
或者在涉及枚举时是否有另一种方式来实现格式化程序?

测试用例here

【问题讨论】:

  • Writes.enumNameWrites 已提供。没有案例类定义,我觉得将scala.Enumeration.Value 作为属性类型而不是E#Value(对于某些E <: scala.Enumeration)很奇怪
  • 你是否在控制SearchRequest 并且它没有被其他库使用?
  • @pme 是的,在控制中。它将与 Slick 一起使用。谢谢!
  • 您是否也对完全替换 scala.Enumeration 的解决方案感兴趣?
  • @pme 是的。据我了解,陪审团决定是否应该使用 Enumeration 类或只使用用例类。任何提供类似枚举功能的方法都可以使用 - 但首选是最佳实践方法,我的意思是使用实际的枚举。

标签: forms scala enums playframework


【解决方案1】:

我将这个库用于任何枚举:enumeratum

使用 Dotty 会有很棒的 枚举,但在那之前我认为移动到 enumeratum 是处理 中的枚举的最佳方式斯卡拉。另见Dotty - Enumerations

另外还有一个play-json 扩展,请参阅Play JSON Extension

这样您的代码将如下所示:

import enumeratum.{ PlayJsonEnum, Enum, EnumEntry }

sealed trait SearchRequest extends EnumEntry

object SearchRequest extends Enum[SearchRequest] with PlayJsonEnum[SearchRequest] {

  val values = findValues

  case object SuperRequest  extends SearchRequest
  case object SimpleRequest extends SearchRequest
  ..
}

本质上,PlayJsonEnum[SearchRequest] 完成了所有工作。

【讨论】:

  • 完美,谢谢!这看起来比标准枚举好得多。一个小的变化是使用 PlayEnum 而不是 PlayJsonEnum,因为它包括 JSON 和 Form 支持。然后在表单映射中您可以使用"myEnum" -> MyEnum.formField,
【解决方案2】:

要将枚举写成字符串,如 cchantep 所说,您可以使用 Writes.enumNameWrites,我们专门用于读取和写入 ID。因此,我们在全局包中为枚举提供了一个 EnumFormat:

package object enums {

  implicit def reads[E <: Enumeration](enum: E): Reads[E#Value] = new Reads[E#Value] {
    def reads(json: JsValue): JsResult[E#Value] = json match {
      case JsNumber(s) =>
        try {
          JsSuccess(enum.apply(s.toInt))
        } catch {
          case _: NoSuchElementException => JsError(s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$s'")
        }
      case _ => JsError("Number value expected")
    }
  }

  implicit def writes[E <: Enumeration]: Writes[E#Value] = new Writes[E#Value] {
    def writes(v: E#Value): JsValue = JsNumber(v.id)
  }

  implicit def formatID[E <: Enumeration](enum: E): Format[E#Value] =
    Format(reads(enum), writes)


  def readsString[E <: Enumeration](enum: E): Reads[E#Value] = new Reads[E#Value] {
    def reads(json: JsValue): JsResult[E#Value] = json match {
      case JsString(s) => {
        try {
          JsSuccess(enum.withName(s))
        } catch {
          case _: NoSuchElementException => JsError(s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$s'")
        }
      }
      case _ => JsError("String value expected")
    }
  }

  implicit def writesString[E <: Enumeration]: Writes[E#Value] = new Writes[E#Value] {
    def writes(v: E#Value): JsValue = JsString(v.toString)
  }

  implicit def formatString[E <: Enumeration](enum: E): Format[E#Value] =
    Format(readsString(enum), writesString)
}

并使用:

object SearchRequest extends Enumeration(1) {
  type SearchRequest = Value

  val ONE /*1*/ , TWO /*2*/ , ETC /*n*/ = Value

  implicit val searchRequestFormat: Format[SearchRequest] = formatID(SearchRequest)
}

【讨论】:

  • 它不应该是必需的,并且当使用Json.writes/format 宏为案例类“生成”/派生实例时,应该从隐式范围解析这个泛型Writes
  • 谢谢,不幸的是,这段代码似乎给出了类型不匹配,我们的格式是 play.api.libs.json.Format[models.MatchFilterType.Value],但 Play 需要 play.api.data.format.Formatter[models.MatchFilterType.Value] "mft" -&gt; Forms.of[MatchFilterType](matchFilterTypeFormat)。我在这里做了一个测试用例github.com/jethrogillgren/play-samples/blob/2.7.x/… 感谢您的进一步帮助!注意SearchRequest是case类,使用枚举MatchFilterType。
【解决方案3】:

将以下内容添加到您的枚举对象中

implicit object MatchFilterTypeFormatter extends Formatter[MatchFilterType.Value] {
    override val format = Some(("format.enum", Nil))
    override def bind(key: String, data: Map[String, String]) = {
      try {
        Right(MatchFilterType.withName(data.get(key).head))
      } catch {
        case e:NoSuchElementException =>  Left(Seq(play.api.data.FormError(key, "Invalid MatchFilterType Enumeration")))
      }
    }
    override def unbind(key: String, value: MatchFilterType.Value) = {
      Map(key -> value.toString)
    }
  }
  implicit val matchFilterTypeFormat = new Format[MatchFilterType.MatchFilterType] {
    def reads(json: JsValue) = JsSuccess(MatchFilterType.withName(json.as[String]))
    def writes(myEnum: MatchFilterType.MatchFilterType) = JsString(myEnum.toString)
  }

然后问题中给出的格式化程序/控制器将起作用。

一个有效的测试用例是here

【讨论】:

  • 一个改进是使这个通用化,这样就不需要为您希望使用的每个枚举进行复制。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-07
相关资源
最近更新 更多