Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 571
mirelon Avatar asked Jan 29 '15 10:01

mirelon


2 Answers

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)

like image 187
Hanan Oanunu Avatar answered Oct 20 '22 04:10

Hanan Oanunu


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

like image 5
4lex1v Avatar answered Oct 20 '22 04:10

4lex1v