spray-json无法处理Either序列

7

我不确定这是否是一个bug,但以下示例在最后几个测试用例中失败:

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)))
}

所有的构建块似乎都可以正常工作,但是对于SeqEither的格式组合失败了,即使我尝试进行详细说明。

我看到以下错误:

[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)))

有什么想法是怎么回事?
2个回答

14

这是Either最令人讨厌的事情之一——LeftRight构造函数都扩展了ProductSerializable,但Either本身并没有,这会导致糟糕的类型推断:

scala> Seq(Left(1), Right(""))
res0: Seq[Product with Serializable with scala.util.Either[Int,String]] = List(Left(1), Right())
由于JsonFormat在其类型参数中是不变的,因此您对A拥有一个实例并不意味着您对Product with Serializable with A也拥有一个实例。在您的情况下,实际上存在一个Either[Int, String]的实例,但是推断类型中多余的垃圾使编译器无法找到它。
如果序列中没有Right,那么类似的情况也会发生。
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
                             ^

你可以通过提供一个类型而不是使用推断的类型来解决这两个问题:

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,""]

在许多情况下,这不是问题,因为您通常会从显式返回Either的方法中获取 Either 值,而不是直接使用 LeftRight 以导致此问题的方式。

作为附注:这就是为什么在定义自己的 ADT 时,应始终将根密封特征(或密封类)扩展为 Product with Serializable。如果标准库设计人员遵循了这个建议,我们都会变得更好。


这修复了我的示例,但不幸的是,它并没有修复我的真实代码 :( 但非常有启发性,谢谢! - acjay

2

我认为如果您将Seqs的元素添加类型注释,则可以编译:

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))

您可以为其指定单个类型:
(Seq(Left(1), Right("")): Either[Int, String]).toJson
(Seq(Left(1), Right("")): Either[Int, String]).toJson(seqFormat(eitherFormat(IntJsonFormat, StringJsonFormat))

我认为问题在于scalac试图确定您提供给Seq的元素之间的公共最小上界,以推导出一个类型(因为标准集合要求其元素具有同质数据类型),而它在没有给予帮助的情况下无法推断出您想要的内容。如果Scala标准库已将extends Product with Serializable添加到抽象类Either定义中,则您不需要这样做,但由于子类型Right和Left都是case class(它们隐式地扩展Product和Serializable),它们包含在推断类型中,这会导致您在使用spray所需的不变类型时遇到问题。

看起来Travis比我先完成了。 :-) - Steve Buzzard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接