为扩展特征的类获取Play JSON JsValueWrapper

时间:2015-02-26 17:22:06

标签: json scala playframework playframework-json

我正在为速度生成JSON,其中单位可能会有所不同。我有一个SpeedUnit特性和扩展它的类(Knots,MetersPerSecond,MilesPerHour)。 JSON Play documentation表示“要将自己的模型转换为JsValues,必须定义隐式Writes转换器并在范围内提供它们。”我得到了它在大多数地方工作,但不是当我有一个类扩展一个特征。我究竟做错了什么?或者是否有我可以或应该使用的Enum变体?

// Type mismatch: found (String, SpeedUnit), required (String, Json.JsValueWrapper)
// at 4th line from bottom:  "speedunit" -> unit

import play.api.libs.json._

trait SpeedUnit {
  // I added this to SpeedUnit thinking it might help, but it didn't.
  implicit val speedUnitWrites = new Writes[SpeedUnit] {
    def writes(x: SpeedUnit) = Json.toJson("UnspecifiedSpeedUnit")
  }
}

class Knots extends SpeedUnit {
  implicit val knotsWrites = new Writes[Knots] {
    def writes(x: Knots) = Json.toJson("KT")
  }
}
class MetersPerSecond extends SpeedUnit {
  implicit val metersPerSecondWrites = new Writes[MetersPerSecond] {
    def writes(x: MetersPerSecond) = Json.toJson("MPS")
  }
}
class MilesPerHour extends SpeedUnit {
  implicit val milesPerHourWrites = new Writes[MilesPerHour] {
    def writes(x: MilesPerHour) = Json.toJson("MPH")
  }
}

// ...

class Speed(val value: Int, val unit: SpeedUnit) {
  implicit val speedWrites = new Writes[Speed] {
    def writes(x: Speed) = Json.obj(
      "value" -> value,
      "speedUnit" -> unit  // THIS LINE DOES NOT TYPE-CHECK
    )
  }
}

1 个答案:

答案 0 :(得分:3)

Writes是类型类的示例,这意味着您需要为给定Writes[A]提供A的单个实例,而不是每个A实例。如果您来自Java背景,请考虑Comparator而不是Comparable

import play.api.libs.json._

sealed trait SpeedUnit
case object Knots extends SpeedUnit
case object MetersPerSecond extends SpeedUnit
case object MilesPerHour extends SpeedUnit

object SpeedUnit {
  implicit val speedUnitWrites: Writes[SpeedUnit] = new Writes[SpeedUnit] {
    def writes(x: SpeedUnit) = Json.toJson(
      x match {
        case Knots => "KTS"
        case MetersPerSecond => "MPS"
        case MilesPerHour => "MPH"
      }
    )
  }
}

case class Speed(value: Int, unit: SpeedUnit)

object Speed {
  implicit val speedWrites: Writes[Speed] = new Writes[Speed] {
    def writes(x: Speed) = Json.obj(
      "value" -> x.value,
      "speedUnit" -> x.unit
    )
  }
}

然后:

scala> Json.toJson(Speed(10, MilesPerHour))
res0: play.api.libs.json.JsValue = {"value":10,"speedUnit":"MPH"}

我已将Writes个实例放在这两种类型的随播对象中,但它们可以转到其他位置(如果您不想在模型中混淆序列化问题,例如)。

您还可以使用Play JSON的功能API简化(或至少简明扼要):

sealed trait SpeedUnit
case object Knots extends SpeedUnit
case object MetersPerSecond extends SpeedUnit
case object MilesPerHour extends SpeedUnit

case class Speed(value: Int, unit: SpeedUnit)

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val speedWrites: Writes[Speed] = (
  (__ \ 'value).write[Int] and
  (__ \ 'speedUnit).write[String].contramap[SpeedUnit] {
    case Knots => "KTS"
    case MetersPerSecond => "MPS"
    case MilesPerHour => "MPH"
  }
)(unlift(Speed.unapply))

你采取哪种方法(功能性或明确性)主要是品味问题。