我试图获取一个共享特征的项目列表,然后将它们转交给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)
)
}
列表的Json.toJson根本不起作用;也没有阅读对应物.当我用类Engines或我的任何单个具体类替换上面代码中的t.cars时,一切正常.
我怎样才能优雅地解决这个问题,让我的Json读者和作家工作?或者,如果Scala Play的Json编码器解码器不适合这样的任务,那么是否有更合适的Json库?
最佳答案 运行您的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找到解串器,因此您需要为Vehicle添加读/写(或格式).这将只委托给该类型的实际格式.
对于写入,实现非常简单,可以根据类型进行模式匹配.对于读取,我正在寻找json中的区别属性,以指示要委派的读取内容.
Note that
play-json
provides helpers so you don’t have to manually implementWrites/Reads
for case classes, so you can writeval engineLoadWrites : Writes[Engine] = Json.writes[Engine]
. This is used in the sample below.
//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))) */