如何在 Scala 的隐式 Json `reads` 中使用特定的应用方法

How to use specific apply method in implicit Json `reads` from Scala

我有一个 class 在其构造函数中采用一些可选的 Enumeration 类型:

case class GPMedia(id: Option[GPID], created: Option[DateTime], active: Option[Boolean], data: Array[Byte], mimeType: Option[GPMediaType.Type], encoding: Option[GPEncoder.Codec], compression: Option[GPCompressor.Type])

我一直在努力创建一个有效的 implicit Json reads 方法。我一直以错误告终,例如:

[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPMedia.scala:57: overloaded method value apply with alternatives:
etc...

我想做的是翻译入站 Json 字符串,将它们变成正确类型的 Option 实例(例如,MIME 类型 "image/png" 在Json 将变成 Option(GPMediaType(v))。GPMediaType 构造函数将映射字符串,并且 return 是一个正确的值(其中之一是 GPMediaType.Unknown)。

这是我目前已经完成的 implicit reads,在 GPMedia class' 伴随对象上实现...

case object GPMedia extends GPRequestLogging {
    implicit val reads: Reads[GPMedia] = (
        (__ \ "id").readNullable[GPID] and
            (__ \ "created").readNullable[DateTime] and
            (__ \ "active").readNullable[Boolean] and
            (__ \ "data").read[Array[Byte]] and
            (__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
            (__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
            (__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
        )(GPMedia.apply _)
}

这行得通,但是当我尝试添加其他 apply() 方法时,一切都变得糟糕了。如何在 Json reads 实现中应用 specific apply 方法?例如,当我添加此 apply 方法时:

def apply(data: Array[Byte]) = new GPMedia(None, None, None, data, None, None, None)

我最终得到:

[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPMedia.scala:60: ambiguous reference to overloaded definition,
[error] both method apply in object GPMedia of type (id: Option[models.GPID], created: Option[org.joda.time.DateTime], active: Option[Boolean], data: Array[Byte], mimeType: Option[utility.GPMediaType.Type], encoding: Option[utility.GPEncoder.Codec], compression: Option[utility.GPCompressor.Type])utility.GPMedia
[error] and  method apply in object GPMedia of type (data: Array[Byte])utility.GPMedia
[error] match expected type ?
[error]         )(GPMedia.apply _)

我尝试了几种不同的方法,例如 (GPMedia.apply(...)),但我似乎无法正确设置参数。

我是整个 Json 隐式 reader/writer 和 Json 解码语法的新手。显然我在这里遗漏了一些东西...

编辑

这是另一个例子,关于我尝试调用特定的 apply 方法:

implicit val reads: Reads[GPMedia] = (
    (__ \ "id").readNullable[GPID] and
        (__ \ "created").readNullable[DateTime] and
        (__ \ "active").readNullable[Boolean] and
        (__ \ "data").read[Array[Byte]] and
        (__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
        (__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
        (__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
    )(v => GPMedia.apply(v.id, v.created, v.active, v.data, v.mimeType, v.encoding, v.compression))

这导致:

[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPMedia.scala:60: type mismatch;
[error]  found   : utility.GPMedia
[error]  required: (Option[models.GPID], Option[org.joda.time.DateTime], Option[Boolean], Array[Byte], Option[utility.GPMediaType.Value], Option[utility.GPEncoder.Value], Option[utility.GPCompressor.Type])
[error]     (which expands to)  (Option[models.GPID], Option[org.joda.time.DateTime], Option[Boolean], Array[Byte], Option[utility.GPMediaType.Value], Option[utility.GPEncoder.Value], Option[utility.GPCompressor.Value])
[error]         )(v => GPMedia.apply(v.id, v.created, v.active, v.data, v.mimeType, v.encoding, v.compression))
[error]                             ^

这是不可能的。编译器不知道使用哪个 apply 方法。这只是使用重载方法的注意事项之一。使它像 "nice" 的唯一方法是重命名方法,或者使用不同名称的重载 apply 方法别名并使用它们。


您的第二次尝试无效,因为编译器期望函数的签名类似于 apply,例如:

(Option[GPID], Option[DateTime], Option[Boolean], Array[Byte], Option[String], Option[String], Option[String]) => GPMedia

但您正在尝试使用:

GPMedia => GPMedia

这不起作用,因为我们还没有 GPMedia 对象,只有元组字段。它看起来更像:

implicit val reads: Reads[GPMedia] = (
    (__ \ "id").readNullable[GPID] and
    (__ \ "created").readNullable[DateTime] and
    (__ \ "active").readNullable[Boolean] and
    (__ \ "data").read[Array[Byte]] and
    (__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
    (__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
    (__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
).tupled.map(v => GPMedia.apply(v._1, v._2, v._3, v._4, v._5, v._6, v._7))

这看起来不太好。通常我们可以这样让它看起来更好:

implicit val reads: Reads[GPMedia] = (
    (__ \ "id").readNullable[GPID] and
    (__ \ "created").readNullable[DateTime] and
    (__ \ "active").readNullable[Boolean] and
    (__ \ "data").read[Array[Byte]] and
    (__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
    (__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
    (__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
).tupled.map(v => GPMedia.apply _ tupled v)

Except 您最终会遇到与开始时相同的问题,因为编译器将无法选择正确的 apply 方法。所以你真的别无选择,只能重命名或使事情变得丑陋。

有可能。

但是您需要指定重载应用方法所需的所有参数 using _: ParameterType 就像我在下面所做的那样,它会起作用。

implicit val reads: Reads[GPMedia] = (
    (__ \ "id").readNullable[GPID] and
      (__ \ "created").readNullable[DateTime] and
      (__ \ "active").readNullable[Boolean] and
      (__ \ "data").read[Array[Byte]] and
      (__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
      (__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
      (__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
    ) (
    v => GPMedia.apply(
      _: GPID,
      _: DateTime, 
      _: Boolean,
      _: Array[Byte],
      _: Option[GPMediaType],
      _: Option[GPEncoder],
      _: Option[GPCompressor]
    )
  )