Scala, spray-json: 通用枚举json格式化
Scala, spray-json: universal enumeration json formatting
我有这样的模型:两个枚举和一个案例 class 具有这些枚举类型的两个字段:
// 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)
我想 parse/generate JSONs to/from 与 spray-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
那么,两个问题:
如何去掉这些 fruitFormat
和 vegetableFormat
声明?
理想情况下,最好不要使枚举对象隐式化,同时保持 enumFormat
函数通用。有没有办法做到这一点?也许,使用 scala.reflect
包或类似的东西。
你不能,Scala 不允许隐式链接,因为它会导致组合爆炸,使编译器变得太慢。
见https://docs.scala-lang.org/tutorials/FAQ/chaining-implicits.html
Scala does not allow two such implicit conversions taking place, however, so one cannot got from A to C using an implicit A to B and another implicit B to C.
您必须为每个要使用的 T
显式生成一个 JsonFormat[T]
。
您只需将 enum.Value
替换为 A#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")
}
}
}
我有这样的模型:两个枚举和一个案例 class 具有这些枚举类型的两个字段:
// 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)
我想 parse/generate JSONs to/from 与 spray-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
那么,两个问题:
如何去掉这些
fruitFormat
和vegetableFormat
声明?理想情况下,最好不要使枚举对象隐式化,同时保持
enumFormat
函数通用。有没有办法做到这一点?也许,使用scala.reflect
包或类似的东西。
你不能,Scala 不允许隐式链接,因为它会导致组合爆炸,使编译器变得太慢。
见https://docs.scala-lang.org/tutorials/FAQ/chaining-implicits.html
Scala does not allow two such implicit conversions taking place, however, so one cannot got from A to C using an implicit A to B and another implicit B to C.
您必须为每个要使用的 T
显式生成一个 JsonFormat[T]
。
您只需将 enum.Value
替换为 A#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")
}
}
}