mirelon
mirelon

Reputation: 4996

Implicit jsonFormat for case class with varargs

I have a case class containing varargs, with an implicit jsonFormat as follows:

import spray.json._
case class Colors(name: String*)
object MyJsonProtocol extends DefaultJsonProtocol {
  implicit val colorFormat = jsonFormat1(Colors)
}
import MyJsonProtocol._
Colors("CadetBlue").toJson

It raises an error:

error: type mismatch;
found   : Color2.type
required: Seq[String] => Color2
Note: implicit value colorFormat is not applicable here because it comes after the application point and it lacks an explicit result type
      implicit val colorFormat = jsonFormat1(Color2)
                                            ^

I have also tried:

implicit val colorFormat = jsonFormat1(Colors.apply)

which caused a different (runtime!) exception:

java.lang.RuntimeException: Cannot automatically determine case class field names and order for 'Colors', please use the 'jsonFormat' overload with explicit field name specification

The following:

implicit val colorFormat = jsonFormat(Colors, "name")

raises the former error

It is even possible to define implicit jsonFormat for case class with varargs?

Upvotes: 6

Views: 4824

Answers (3)

Hanan Oanunu
Hanan Oanunu

Reputation: 179

I also experienced this exception, however in my case it was due to a 'val' definition within the case class (not as passed argument).

Problematic case class:

case class Color(name: String) {
  val code: Int = ...
}

Working case class:

case class Color(name: String) {
  def code: Int = ...
}

Which is kind of misfortunate as, now the 'code' will be computed on each call.

(Used Akka Http Version - 10.1.1)

Upvotes: 12

4lex1v
4lex1v

Reputation: 21567

It should work perfectly, probably you have some ambiguity in implicits. This works perfectly:

import spray.json._, DefaultJsonProtocol._

case class Test(param: String*)
object Test {
  implicit val testFormat = jsonFormat1(Test.apply)
}

Like a best practice advice, don't use Protocol pattern, it leads to large implicit errors in big project, always define implicits in your companion object (of cause there are exception cases). Another point avoid inheritance, it's not really needed.

Scala * pattern is just a sugar for Seq type constructor, so it should find seqFormat (un)marshaller for this cases.

Update

It doesn't work because Spray uses ClassManifest to extract field names from the copy function, but the compiler doesn't generate this function for case classes with varargs in constructor:

case class Test(params: String*)
def man[A: ClassManifest] = implicitly[ClassManifest[A]]
man[Test].erasure.getDeclaredMethods.filter(_.getName.contains("copy"))
res4: Array[java.lang.reflect.Method] = Array()

scala> case class Test(param: String)
defined class Test

scala> man[Test].erasure.getDeclaredMethods.filter(_.getName.contains("copy"))
warning: there was one deprecation warning; re-run with -deprecation for details
res5: Array[java.lang.reflect.Method] = Array(public Test Test.copy(java.lang.String), public java.lang.String Test.copy$default$1())

So you need to provide the field name manually. BTW i didn't know this before

Upvotes: 5

Mustafa Simav
Mustafa Simav

Reputation: 1019

I suggest you to define your case class with name: List[String] and create companion object with custom apply function. You are still able to create objects like Colors("blue", "green").

case class Colors(name: List[String])

object Colors {
  def apply(name: String*): Colors = new Colors(name.toList)
  def apply(names: List[String]): Colors = new Colors(names)
}


Colors("blue", "green")

Upvotes: 0

Related Questions