在Play for Scala中将异构列表转换为Json和从Json转换

时间:2016-02-14 20:44:29

标签: json scala playframework

我正在尝试获取具有共同特征的项目列表,并将它们转交给Json。我在这里介绍的例子是一辆带有发动机和汽车的火车。创建火车分为三类:发动机,乘用车和货车。 (我认为一个简单的基于现实的例子最容易理解,它也比我试图解决的问题复杂得多。)

列车的各个部分定义如下:

package models

sealed trait Vehicle {
  val kind: String
  val maxSpeed: Int = 0
  def load: Int
}

case class Engine(override val maxSpeed: Int, val kind: String, 
  val power: Float, val range: Int) extends Vehicle {
  override val load: Int = 0
}

case class FreightCar(override val maxSpeed: Int, val kind: String, 
  val load: Int) extends Vehicle {}

case class PassengerCar(override val maxSpeed: Int, val kind: String, 
  val passengerCount: Int) extends Vehicle {
  override def load: Int = passengerCount * 80
}

(文件Vehicle.scala)

列车定义为:

package models

import scala.collection.mutable
import play.api.Logger
import play.api.libs.json._

case class Train(val name: String, val cars: List[Vehicle]) {
  def totalLoad: Int = cars.map(_.load).sum
  def maxSpeed: Int = cars.map(_.maxSpeed).min
}

object Train {
   def save(train: Train) {
   Logger.info("Train saved ~ Name: " + train.name)
  }
}

(文件Train.scala)

正如您所见,通过将“汽车”添加到列车中存储的列表来创建火车。

我的问题在将我的火车转换为Json或试图从Json读取时产生了问题。这是我目前的代码:

package controllers

import play.api.mvc._
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.data.validation.ValidationError
import play.api.libs.functional.syntax._
import models.Vehicle
import models.Engine
import models.FreightCar
import models.PassengerCar
import models.Train

class Trains extends Controller {

implicit val JsPathWrites = Writes[JsPath](p => JsString(p.toString))

implicit val ValidationErrorWrites =
  Writes[ValidationError](e => JsString(e.message))

 implicit val jsonValidateErrorWrites = (
  (JsPath \ "path").write[JsPath] and
  (JsPath \ "errors").write[Seq[ValidationError]]
  tupled
)

implicit object engineLoadWrites extends Writes[Engine] {
  def writes(e: Engine) = Json.obj(
    "maxSpeed" -> Json.toJson(e.maxSpeed),
    "kind" -> Json.toJson(e.kind),
    "power" -> Json.toJson(e.power),
    "range" -> Json.toJson(e.range)
  )
}

implicit object freightCarLoadWrites extends Writes[FreightCar] {
  def writes(fc: FreightCar) = Json.obj(
    "maxSpeed" -> Json.toJson(fc.maxSpeed),
    "kind" -> Json.toJson(fc.kind),
    "load" -> Json.toJson(fc.load)
  )
}

implicit object passengerCarLoadWrites extends Writes[PassengerCar] {
  def writes(pc: PassengerCar) = Json.obj(
    "maxSpeed" -> Json.toJson(pc.maxSpeed),
    "kind" -> Json.toJson(pc.kind),
    "passengerCount" -> Json.toJson(pc.passengerCount)
  )
}

implicit object trainWrites extends Writes[Train] {
  def writes(t: Train) = Json.obj(
    "name" -> Json.toJson(t.name),
    "cars" -> Json.toJson(t.cars)   // Definitely not correct!
  )
}

/* --- Writes above, Reads below --- */

implicit val engineReads: Reads[Engine] = (
  (JsPath \ "maxSpeed").read[Int] and
  (JsPath \ "kind").read[String] and
  (JsPath \ "power").read[Float] and
  (JsPath \ "range").read[Int]
)(Engine.apply _)

implicit val freightCarReads: Reads[FreightCar] = (
  (JsPath \ "maxSpeed").read[Int] and
  (JsPath \ "kind").read[String] and
  (JsPath \ "load").read[Int]
)(FreightCar.apply _)

implicit val passengerCarReads: Reads[PassengerCar] = (
  (JsPath \ "maxSpeed").read[Int] and
  (JsPath \ "kind").read[String] and
  (JsPath \ "passengerCount").read[Int]
)(PassengerCar.apply _)

implicit val joistReads: Reads[Train] = (
  (JsPath \ "name").read[String](minLength[String](2)) and
  (JsPath \ "cars").read[List[Cars]]  // Definitely not correct!
)(Train.apply _)

/**
* Validates a JSON representation of a Train.
*/
  def save = Action(parse.json) { implicit request =>
    val json = request.body
    json.validate[Train].fold(
      valid = { train =>
        Train.save(train)
        Ok("Saved")
      },
      invalid = {
        errors => BadRequest(Json.toJson(errors))
      }
    )
  }
}

(File Trains.scala)

为FreightCars列表创建和使用Json的所有代码,Engines可以工作,但是我无法创建Json来同时处理所有三种类型,例如:

implicit object trainWrites extends Writes[Train] {
   def writes(t: Train) = Json.obj(
     "name" -> Json.toJson(t.name),
     "cars" -> Json.toJson(t.cars)
   )
 }

List的Json.toJson根本不起作用;也没有阅读对应物。当我使用类Engines或任何单个具体类替换上面代码中的t.cars时,一切正常。

我如何优雅地解决这个问题,让我的Json读者和作家工作?或者,如果Scala Play的Json编码器解码器不适合这样的任务,那么是否有更合适的Json库?

2 个答案:

答案 0 :(得分:1)

运行Writes代码会返回以下错误(提供了修复内容的线索):

Error:(78, 27) No Json deserializer found for type Seq[A$A90.this.Vehicle]. Try to implement an implicit Writes or Format for this type.
"cars" -> Json.toJson(t.cars)   // Definitely not correct!
                     ^

Vehicle没有找到解串器,因此您需要为Reads/Writes添加Format(或Vehicle)。这将只委托给该类型的实际Format

writes的实现非常简单,只需对类型进行模式匹配即可。对于reads我在json中寻找区别属性,以指示Reads委托给谁。

  

请注意,play-json提供了帮助,因此您不必为案例类手动实施Writes/Reads,因此您可以编写val engineLoadWrites : Writes[Engine] = Json.writes[Engine]。这在下面的示例中使用。   

//Question code above, then ...

val engineFormat = Json.format[Engine]
val freightCarFormat = Json.format[FreightCar]
val passengerCarFormat = Json.format[PassengerCar]

implicit val vehicleFormat = new Format[Vehicle]{
  override def writes(o: Vehicle): JsValue = {
    o match {
      case e : Engine => engineFormat.writes(e)
      case fc : FreightCar => freightCarFormat.writes(fc)
      case pc : PassengerCar => passengerCarFormat.writes(pc)
    }
  }

  override def reads(json: JsValue): JsResult[Vehicle] = {
    (json \ "power").asOpt[Int].map{ _ =>
      engineFormat.reads(json)
    }.orElse{
      (json \ "passengerCount").asOpt[Int].map{ _ =>
        passengerCarFormat.reads(json)
      }
    }.getOrElse{
      //fallback to FreightCar
      freightCarFormat.reads(json)
    }
  }
}


implicit val trainFormat = Json.format[Train]


val myTrain = Train(
  "test",
  List(
    Engine(100, "e-1", 1.0.toFloat, 100),
    FreightCar(100, "f-1", 20),
    PassengerCar(100, "pc", 10)
  )
)

val myTrainJson = trainFormat.writes(myTrain) 
/** => myTrainJson: play.api.libs.json.JsObject = {"name":"test","cars":[{"maxSpeed":100,"kind":"e-1","power":1.0,"range":100},{"maxSpeed":100,"kind":"f-1","load":20},{"maxSpeed":100,"kind":"pc","passengerCount":10}]} */

val myTrainTwo = myTrainJson.as[Train]
/* => myTrainTwo: Train = Train(test,List(Engine(100,e-1,1.0,100), FreightCar(100,f-1,20), PassengerCar(100,pc,10))) */

答案 1 :(得分:0)

我相信您的问题是您在List中使用了可变Train.scala,而您在Trains.scala中使用了不可变的变体。我甚至不确定Play为可变列表提供格式化程序,但我确信它对不可变列表有效。

尝试删除

import scala.collection.mutable

看看它是否有效。

顺便说一句,如果它们直接对应于您的案例类,则无需手动定义读取和写入。对于案例类,Play提供默认ReadsWritesFormat(如果您需要同时使用两者):

implicit val engineFormat = Json.format[Engine]