Scala's Play中具有多个案例类(Sum Type)的特征的Json序列化



我经常需要序列化/反序列化和类型(如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)
    )
  }
}

我认为这很简单,如果你想避免为每个显式子类型编写代码,也许你可以使用反射,直接使用杰克逊或其他一些支持反射的json库。或者编写自己的宏以从子类型列表生成格式。

对于在我的 json 酸洗库 Prickle 中序列化和类型的问题,我有一个系统的解决方案。类似的想法也可以用于Play。仍然需要一些配置代码,但它的高信号/噪声,例如最终代码,例如:

implicit val fruitPickler = CompositePickler[Fruit].concreteType[Apple].concreteType[Lemon]

与超类型关联的复合拾取器为每个已知的子类型配置一个PicklerPair(即总和类型选项(。关联在配置时设置。

在酸洗期间,描述符将发送到 json 流中,描述记录是哪个子类型。

在解酸处理期间,将从 json 中读出描述符,然后用于查找子类型的相应Unpickler

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

相关内容

最新更新