Scala, spray-json:通用枚举JSON格式化

8

我有这样的模型:两个枚举和一个案例类,其中包含这些枚举类型的两个字段:

// see later, why objects are implicit
implicit object Fruits extends Enumeration {
  val Apple = Value("apple")
  val Orange = Value("orange")
}

implicit object Vegetables extends Enumeration {
  val Potato = Value("potato")
  val Cucumber = Value("cucumber")
  val Tomato = Value("tomato")
}

type Fruit = Fruits.Value
type Vegetable = Vegetables.Value

case class Pair(fruit: Fruit, vegetable: Vegetable)

我希望使用spray-json来解析/生成与Pairs相关的JSON。我不想为水果和蔬菜分别声明单独的JsonFormat。因此,我想要做类似于这样的事情:
import spray.json._
import spray.json.DefaultJsonProtocol._

// enum is implicit here, that's why we needed implicit objects
implicit def enumFormat[A <: Enumeration](implicit enum: A): RootJsonFormat[enum.Value] =
  new RootJsonFormat[enum.Value] {
    def read(value: JsValue): enum.Value = value match {
      case JsString(s) =>
        enum.withName(s)
      case x =>
        deserializationError("Expected JsString, but got " + x)
    }

    def write(obj: enum.Value) = JsString(obj.toString)
  }

// compilation error: couldn't find implicits for JF[Fruit] and JF[Vegetable]
implicit val pairFormat = jsonFormat2(Pair)

// expected value:
// spray.json.JsValue = {"fruit":"apple","vegetable":"potato"}
// but actually doesn't even compile
Pair(Fruits.Apple, Vegetables.Potato).toJson

遗憾的是,enumFormat 不会为 jsonFormat2 生成隐式值。如果我在 pairFormat 之前手动编写两个隐式声明,用于水果和蔬菜格式,那么 json 编组将起作用:

implicit val fruitFormat: RootJsonFormat[Fruit] = enumFormat(Fruits)
implicit val vegetableFormat: RootJsonFormat[Vegetable] = enumFormat(Vegetables)

implicit val pairFormat = jsonFormat2(Pair)

// {"fruit":"apple","vegetable":"potato"}, as expected
Pair(Fruits.Apple, Vegetables.Potato).toJson

那么,有两个问题:

  1. 如何摆脱这些fruitFormatvegetableFormat声明?

  2. 理想情况下,不让枚举对象变得隐式,同时保持enumFormat函数的通用性,有办法实现吗?也许可以使用scala.reflect包或类似的东西。

2个回答

14
你只需要用 A#Value 替换 enum.Value
观看 spray-json #200,你可以找到一个良好定义的隐式 enumFormat 的例子,稍微修改一下以利用隐式的 enu 检索。
implicit def enumFormat[T <: Enumeration](implicit enu: T): RootJsonFormat[T#Value] =
  new RootJsonFormat[T#Value] {
    def write(obj: T#Value): JsValue = JsString(obj.toString)
    def read(json: JsValue): T#Value = {
      json match {
        case JsString(txt) => enu.withName(txt)
        case somethingElse => throw DeserializationException(s"Expected a value from enum $enu instead of $somethingElse")
      }
    }
}

0

你不能这样做,Scala不允许隐式链接,因为这会导致组合爆炸,使编译器变得太慢。

请参见https://docs.scala-lang.org/tutorials/FAQ/chaining-implicits.html

然而,Scala不允许发生两个这样的隐式转换,因此,不能使用一个隐式A到B和另一个隐式B到C从A到C。

您将需要明确地为每个要使用的T生成一个JsonFormat[T]


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