Not sure this is a bug, but the following demo fails on the final cases:
import spray.json._
import DefaultJsonProtocol._
object SprayTest {
1.toJson
"".toJson
(Left(1): Either[Int, String]).toJson
(Right(""): Either[Int, String]).toJson
Seq(1).toJson
Seq("").toJson
Seq(Left(1), Right("")).toJson
Seq(Left(1), Right("")).toJson(seqFormat(eitherFormat(IntJsonFormat, StringJsonFormat)))
}
So all the building blocks appear to work, but the composition of the format for Seq
and Either
fails, even if I try to spoon-feed it.
I see the following errors:
[error] SprayTest.scala:11: Cannot find JsonWriter or JsonFormat type class for Seq[Product with Serializable with scala.util.Either[Int,String]]
[error] Seq(Left(1), Right("")).toJson
[error] ^
[error] SprayTest.scala:12: type mismatch;
[error] found : spray.json.DefaultJsonProtocol.JF[Either[Int,String]]
[error] (which expands to) spray.json.JsonFormat[Either[Int,String]]
[error] required: spray.json.JsonFormat[Product with Serializable with scala.util.Either[Int,String]]
[error] Note: Either[Int,String] >: Product with Serializable with scala.util.Either[Int,String] (and spray.json.DefaultJsonProtocol.JF[Either[Int,String]] <: spray.json.JsonFormat[Either[Int,String]]), but trait JsonFormat is invariant in type T.
[error] You may wish to define T as -T instead. (SLS 4.5)
[error] Seq(Left(1), Right("")).toJson(seqFormat(eitherFormat(IntJsonFormat, StringJsonFormat)))
Any idea what gives?
This is one of the most annoying things about Either
—the Left
and Right
constructors both extend Product
and Serializable
, but Either
itself doesn't, which leads to awful inferred types:
scala> Seq(Left(1), Right(""))
res0: Seq[Product with Serializable with scala.util.Either[Int,String]] = List(Left(1), Right())
Because JsonFormat
is invariant in its type parameter, the fact that you have an instance for A
doesn't mean you have an instance for Product with Serializable with A
. In your case specifically, there is actually an instance for Either[Int, String]
, but the extra garbage in the inferred type means the compiler can't find it.
A similar thing happens if you don't have a Right
in the sequence:
scala> Seq(Left(1), Left(2)).toJson
<console>:18: error: Cannot find JsonWriter or JsonFormat type class for Seq[scala.util.Left[Int,Nothing]]
Seq(Left(1), Left(2)).toJson
^
You can fix both problems by providing a type instead of using the inferred one:
scala> val xs: Seq[Either[Int, String]] = Seq(Left(1), Right(""))
xs: Seq[Either[Int,String]] = List(Left(1), Right())
scala> xs.toJson
res1: spray.json.JsValue = [1,""]
In many cases this isn't an issue, since you'll often get your Either
values from methods that explicitly return an Either
instead of using Left
and Right
directly in ways that lead to this problem.
As a footnote: this is why you should always have your root sealed trait (or sealed class) extend Product with Serializable
when you're defining your own ADTs. We'd all be a lot better off if the standard library designers had followed that advice.