8
votes

I have the following code which uses spray-json to deserialise some JSON into a case class, via the parseJson method.

Depending on where the implicit JsonFormat[MyCaseClass] is defined (in-line or imported from companion object), and whether there is an explicit type provided when it is defined, the code may not compile.

I don't understand why importing the implicit from the companion object requires it to have an explicit type when it is defined, but if I put it inline, this is not the case?

Interestingly, IntelliJ correctly locates the implicit parameters (via cmd-shift-p) in all cases.

I'm using Scala 2.11.7.

Broken Code - Wildcard import from companion object, inferred type:

import SampleApp._
import spray.json._

class SampleApp {
  import MyJsonProtocol._
  val inputJson = """{"children":["a", "b", "c"]}"""
  println(s"Deserialise: ${inputJson.parseJson.convertTo[MyCaseClass]}")
}

object SampleApp {
  case class MyCaseClass(children: List[String])

  object MyJsonProtocol extends DefaultJsonProtocol {
    implicit val myCaseClassSchemaFormat = jsonFormat1(MyCaseClass)
  }
}

Results in:

Cannot find JsonReader or JsonFormat type class for SampleAppObject.MyCaseClass

Note that the same thing happens with an explicit import of the myCaseClassSchemaFormat implicit.

Working Code #1 - Wildcard import from companion object, explicit type:

Adding an explicit type to the JsonFormat in the companion object causes the code to compile:

import SampleApp._
import spray.json._

class SampleApp {
  import MyJsonProtocol._
  val inputJson = """{"children":["a", "b", "c"]}"""
  println(s"Deserialise: ${inputJson.parseJson.convertTo[MyCaseClass]}")
}

object SampleApp {
  case class MyCaseClass(children: List[String])

  object MyJsonProtocol extends DefaultJsonProtocol {
    //Explicit type added here now
    implicit val myCaseClassSchemaFormat: JsonFormat[MyCaseClass] = jsonFormat1(MyCaseClass)
  }
}

Working Code #2 - Implicits inline, inferred type:

However, putting the implicit parameters in-line where they are used, without the explicit type, also works!

import SampleApp._
import spray.json._

class SampleApp {
  import DefaultJsonProtocol._

  //Now in-line custom JsonFormat rather than imported
  implicit val myCaseClassSchemaFormat = jsonFormat1(MyCaseClass)

  val inputJson = """{"children":["a", "b", "c"]}"""
  println(s"Deserialise: ${inputJson.parseJson.convertTo[MyCaseClass]}")
}

object SampleApp {
  case class MyCaseClass(children: List[String])
}
1
This is one of those "it hurts when I do this" questions where the best answer is almost certainly "well don't do that". In my experience implicit values lacking type annotations are one of the most common sources of confusion, weird cross-version differences in behavior, etc., in Scala.Travis Brown
Hi Travis - indeed, this was an interesting bug to work around, but I guess next time type annotations will be my first port of call for similar issues! Not sure if this counts as a Scala bug or not, but may put something onto the mailing list / look at raising an issue just in case.MrProper
The compiler spits out an error message saying ‘implicit method whatever is not applicable here because it comes after the application point and it lacks an explicit result type’ so at least the error's trivial to diagnose and fix :)Hugh
Huw, did you get an error message like that using any of the code examples below? I only get the pasted error, Cannot find JsonReader or JsonFormat type class for SampleAppObject.MyCaseClass, but yours would be much more useful.MrProper

1 Answers

3
votes

After searching for the error message Huw mentioned in his comment, I was able to find this StackOverflow question from 2010: Why does this explicit call of a Scala method allow it to be implicitly resolved?

This led me to this Scala issue created in 2008, and closed in 2011: https://issues.scala-lang.org/browse/SI-801 ('require explicit result type for implicit conversions?')

Martin stated:

I have implemented a slightly more permissive rule: An implicit conversion without explicit result type is visible only in the text following its own definition. That way, we avoid the cyclic reference errors. I close for now, to see how this works. If we still have issues we migth come back to this.

This holds - if I re-order the breaking code so that the companion object is declared first, then the code compiles. (It's still a little weird!)

(I suspect I don't see the 'implicit method is not applicable here' message because I have an implicit value rather than a conversion - though I'm assuming here that the root cause is the same as the above).