为扩展特征的类获取Play JSON JsValueWrapper [英] Getting a Play JSON JsValueWrapper for a class that extends a trait
问题描述
我正在为速度生成JSON,其中单位可能会有所不同.我有一个SpeedUnit特性和扩展它的类(结,MetersPerSecond,MilesPerHour). JSON Play文档说:要将自己的模型转换为JsValues,必须定义隐式写入转换器并在范围内提供它们."我在大多数地方都可以使用它,但是当我上一堂延伸特质的课程时却无法使用.我究竟做错了什么?还是有我应该或应该使用的Enum变体?
I'm generating JSON for a speed where the units may vary. I have a SpeedUnit trait and classes that extend it (Knots, MetersPerSecond, MilesPerHour). The JSON Play documentation said "To convert your own models to JsValues, you must define implicit Writes converters and provide them in scope." I got that to work in most places but not when I had a class extending a trait. What am I doing wrong? Or is there an Enum variant I could or should have used instead?
// 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
)
}
}
推荐答案
Writes
是类型类的示例,这意味着对于给定的A
,您需要一个Writes[A]
的单个实例,而不是每个A
实例.如果您来自Java背景,请考虑使用Comparator
而不是Comparable
.
Writes
is an example of a type class, which means you need a single instance of a Writes[A]
for a given A
, not for every A
instance. If you're coming from a Java background, think Comparator
instead of 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
实例放置在这两种类型的伴随对象中,但是它们可以在其他地方使用(例如,如果您不想在模型中混淆序列化问题).
I've put the Writes
instances in the companion objects for the two types, but they can go elsewhere (if you don't want to mix up serialization concerns in your model, for example).
您还可以使用Play JSON的功能API来简化(或至少简明扼要)这一点:
You can also simplify (or at least concise-ify) this a lot with Play JSON's functional 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))
您采用哪种方法(实用或显式)很大程度上取决于品味.
Which approach you take (functional or explicit) is largely a matter of taste.
这篇关于为扩展特征的类获取Play JSON JsValueWrapper的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!