【问题标题】:Getting a Play JSON JsValueWrapper for a class that extends a trait为扩展特征的类获取 Play JSON JsValueWrapper
【发布时间】:2015-02-26 17:22:05
【问题描述】:

我正在为 速度 生成 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
    )
  }
}

【问题讨论】:

    标签: json scala playframework playframework-json


    【解决方案1】:

    Writes 是类型类的一个示例,这意味着对于给定的A,您需要一个Writes[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))
    

    您采用哪种方法(函数式或显式)很大程度上取决于个人喜好。

    【讨论】:

    • 非常感谢@TravisBrown,看起来真的很棒。我确实喜欢函数式方法。您的两个解决方案看起来都不错。如果他们成功了,我会回来接受这个答案。
    • 它确实有效@TravisBrown。再一次,这是一个美丽的答案,也非常有帮助和教育意义。
    猜你喜欢
    • 2019-05-11
    • 1970-01-01
    • 2016-07-04
    • 1970-01-01
    • 2016-09-13
    • 2015-06-03
    • 1970-01-01
    • 2012-12-19
    • 1970-01-01
    相关资源
    最近更新 更多