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

1k views Asked by At

I have a class that takes a few optional Enumeration types in its constructor:

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])

I've been struggling to create an implicit Json reads method that works. I keep ending up with errors such as:

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

What I'm trying to do is translate the inbound Json strings, turning them into the right kind of Option instance (eg., a MIME type "image/png" in the Json would turn into Option(GPMediaType(v)). The GPMediaType constructor will map the string, and return a correct value (one of which is GPMediaType.Unknown).

Here's the implicit reads that I've worked up so far, implemented on the GPMedia class' companion object...

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 _)
}

This works, but when I try to add other apply() methods, it all goes to heck. How can I apply a specific apply method in the Json reads implementation? For example, when I add this apply method:

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

I end up with:

[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 _)

I've tried a few different approaches, such as (GPMedia.apply(...)) but I can't seem to get the parameters right.

I'm new to the whole Json implicit reader/writer and Json decoding syntax. Clearly I'm missing something here...

Edit

Here's another example, regarding my attempt to call a specific apply method:

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))

This results in:

[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]                             ^
2

There are 2 answers

1
Michael Zajac On BEST ANSWER

This is not possible. The compiler does not know which apply method to use. This is just one of the caveats of using overloaded methods. The only way you can make this like "nice" is to rename the methods, or alias the overloaded apply methods with different names and use those.


Your second attempt does not work because the compiler is expecting a function with a signature similar to apply, like:

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

But you're trying to use:

GPMedia => GPMedia

Which doesn't work, because we don't yet have a GPMedia object, just the tupled fields. It would look more like:

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))

Which does not look good. Usually we can make it look better like this:

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 that you will end up with the same problem that you started with, because the compiler will not be able to choose the correct apply method. So you really have no choice but to rename or make things ugly.

0
Shahbaz Shueb On

It is possible.

But You need to specify all the parameters required for the overloaded apply method using _: ParameterType like I have done below and it will work.

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]
    )
  )