Scala's Play 中具有多个案例类(总和类型)的特征的 Json 序列化
Posted
技术标签:
【中文标题】Scala\'s Play 中具有多个案例类(总和类型)的特征的 Json 序列化【英文标题】:Json Serialization for Trait with Multiple Case Classes (Sum Types) in Scala's PlayScala's Play 中具有多个案例类(总和类型)的特征的 Json 序列化 【发布时间】:2013-08-29 13:38:05 【问题描述】:我经常需要序列化/反序列化 sum 类型(如 Either[S,T]
),但我还没有找到通用或优雅的方法来做到这一点。这是一个示例类型(基本上等同于Either
)
sealed trait OutcomeType
case class NumericOutcome(units: String) extends OutcomeType
case class QualitativeOutcome(outcomes: List[String]) extends OutcomeType
这是我对实现序列化的伴生对象的最大努力。它可以工作,但是为每种 sum 类型一遍又一遍地写这些东西是非常令人厌烦的。有什么建议可以让它更好和/或更通用?
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)
)
【问题讨论】:
你试过 json4s json4s.org 吗?此外,如果您想在游戏中使用它,那么您应该看这里:github.com/tototoshi/play-json4s 或自己实现。 我觉得不错。你能把你的最大努力更新到play2.5吗?谢谢! 没关系,我在 play2.5 中找到了如何做到这一点并将其放在答案中。 【参考方案1】:我认为这很简单,如果您想避免为每个显式子类型编写代码,也许您可以使用反射来完成,直接使用 jackson 或其他支持反射的 json 库。或者编写您自己的宏以从子类型列表中生成格式。
【讨论】:
【参考方案2】:对于我的 json 酸洗库 Prickle 中序列化 sum-types 的问题,我有一个系统的解决方案。 Play 也可以采用类似的想法。仍然需要一些配置代码,但它的信号/噪声很高,例如最终代码如下:
implicit val fruitPickler = CompositePickler[Fruit].concreteType[Apple].concreteType[Lemon]
与超类型关联的CompositePicklers 为每个已知子类型配置一个PicklerPair
(即总和类型选项)。关联是在配置时设置的。
在pickling 期间,一个描述符被发送到描述记录是哪个子类型的 json 流中。
在unpickling期间,描述符从json中读出,然后用于为子类型定位适当的Unpickler
【讨论】:
【参考方案3】:为 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])
【讨论】:
以上是关于Scala's Play 中具有多个案例类(总和类型)的特征的 Json 序列化的主要内容,如果未能解决你的问题,请参考以下文章