我经常需要序列化/反序列化和类型(如Either[S,T]
),我还没有找到一种通用或优雅的方法来实现它。这是一个示例类型(基本上等同于Either
)
sealed trait OutcomeType
case class NumericOutcome(units: String) extends OutcomeType
case class QualitativeOutcome(outcomes: List[String]) extends OutcomeType
这是我在实现序列化的伴侣对象上的最大努力。它可以工作,但是为每种总和类型反复编写这些类型的东西是非常烦人的。是否有任何建议使其更好和/或更一般?
import play.api.libs.json._
import play.api.libs.functional.syntax._
object OutcomeType {
val fmtNumeric = Json.format[NumericOutcome]
val fmtQualitative = Json.format[QualitativeOutcome]
implicit object FormatOutcomeType extends Format[OutcomeType] {
def writes(o: OutcomeType) = o match {
case n@NumericOutcome(_) => Json.obj("NumericOutcome" -> Json.toJson(n)(fmtNumeric))
case q@QualitativeOutcome(_) => Json.obj("QualitativeOutcome" -> Json.toJson(q)(fmtQualitative))
}
def reads(json: JsValue) = (
Json.fromJson(json \ "NumericOutcome")(fmtNumeric) orElse
Json.fromJson(json \ "QualitativeOutcome")(fmtQualitative)
)
}
}
答案 0 :(得分:1)
我认为这就像你可以做到的一样简单,如果你想避免为每个显式子类型编写代码,也许你可以用反射来做,直接使用jackson或者其他一些带有反射支持的json库。或者编写自己的宏以从子类型列表中生成格式。
答案 1 :(得分:0)
我有一个系统的解决方案,用于在我的json酸洗库Prickle中序列化和类型的问题。 Play可以采用类似的想法。仍然需要一些配置代码,但其高信号/噪声,例如最终代码如:
implicit val fruitPickler = CompositePickler[Fruit].concreteType[Apple].concreteType[Lemon]
CompositePicklers为每个已知子类型配置了一个PicklerPair
(即总和类型选项)。关联在配置时设置。
在pickling期间,描述符被发送到json流中,描述记录所属的子类型。
在unpickling期间,描述符从json中读出,然后用于为子类型找到适当的Unpickler
答案 2 :(得分:0)
为play 2.5更新的示例:
object TestContact extends App {
sealed trait Shape
object Shape {
val rectFormat = Json.format[Rect]
val circleFormat = Json.format[Circle]
implicit object ShapeFormat extends Format[Shape] {
override def writes(shape: Shape): JsValue = shape match {
case rect: Rect =>
Json.obj("Shape" ->
Json.obj("Rect" ->
Json.toJson(rect)(rectFormat)))
case circle: Circle =>
Json.obj("Shape" ->
Json.obj("Circle" ->
Json.toJson(circle)(circleFormat)))
}
override def reads(json: JsValue): JsResult[Shape] = {
json \ "Shape" \ "Rect" match {
case JsDefined(rectJson) => rectJson.validate[Rect](rectFormat)
case _ => json \ "Shape" \ "Circle" match {
case JsDefined(circleJson) => circleJson.validate[Circle](circleFormat)
case _ => JsError("Not a valide Shape object.")
}
}
}
}
}
case class Rect(width: Double, height: Double) extends Shape
case class Circle(radius: Double) extends Shape
val circle = Circle(2.1)
println(Json.toJson(circle))
val rect = Rect(1.3, 8.9)
println(Json.toJson(rect))
var json = Json.obj("Shape" -> Json.obj("Circle" -> Json.obj("radius" -> 4.13)))
println(json.validate[Shape])
json =
Json.obj("Shape" ->
Json.obj("Rect" ->
Json.obj("width" -> 23.1, "height" -> 34.7)))
println(json.validate[Shape])
}