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.
I think if you add type ascriptions to the elements of the Seqs It'll compile:
Seq(Left(1): Either[Int, String], Right(""): Either[Int, String]).toJson
Seq(Left(1): Either[Int, String], Right(""): Either[Int, String]).toJson(seqFormat(eitherFormat(IntJsonFormat, StringJsonFormat))
you can also give it a single type ascription:
(Seq(Left(1), Right("")): Either[Int, String]).toJson
(Seq(Left(1), Right("")): Either[Int, String]).toJson(seqFormat(eitherFormat(IntJsonFormat, StringJsonFormat))
I think the problem is scalac's trying to determine the common least upper bounds between the elements you're providing to Seq to derive a single type (since standard collections require homogeneous data types for their elements) and it doesn't infer what you want it to without giving it help. If the scala standard library had added extends Product with Serializable to the abstract class Either definition you wouldn't need to do this, but since both the subtypes Right and Left are case classes (which implicitly do extend Product and Serializable), they get included in the inferred type, which is causing you issues with the invariant type required by spray.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With