我有两个继承自普通父级的类。我希望在父级中有一个通用的JSON读取器,它将根据提供的JSON返回一个合适的子级。使用下面的示例代码段更容易解释这一点;
import play.api.libs.json.{JsPath, JsonValidationError, Reads}
sealed abstract class Animal(sound: String)
case class Goat(hooves: String) extends Animal("meh")
case class Cat(needsMilk: Boolean) extends Animal("meow")
val json ="""{"type": "goat", "hooves": "All good for climbing trees"}"""
object Animal {
val isSupportedAnimal: Reads[String] =
Reads.StringReads.filter(JsonValidationError("Unsupported animal"))(str => {
List("goat", "cat").contains(str)
})
val animalReads: Reads[Animal] = ((JsPath \ "type").read[String](isSupportedAnimal) and
//if animal is cat, use the cat specific reads and return a cat object
//if animal is goat, use goat specific reads and return a goat
)()
}
鉴于代码段中的json
,我希望拥有Goat
对象,因为指定的类型为goat
。
我是scala的新手,所以我可能会以错误的方式解决问题。欢迎提出建议。
答案 0 :(得分:4)
使用Map
:
sealed abstract class Animal(val sound: String) // You probably want a val here, btw
final case class Goat(hooves: String) extends Animal("meh")
final case class Cat(needsMilk: Boolean) extends Animal("meow")
object Animal {
val readers: Map[String, Reads[_ <: Animal]] = Map(
"goat" -> implicitly[Reads[Goat]],
"cat" -> implicitly[Reads[Cat]],
// Sidenote: Trailing commas ^ make future modification easy
)
// Bonus: Set[String] <: String => Boolean, so you get isSupportedAnimal for free
// val isSupportedAnimal: String => Boolean = readers.keys
implicit val animalReads: Reads[Animal] = new Reads[Animal] {
def reads(s: JsValue): JsResult[Animal] = {
val tpe = (s \ "type").as[String]
val read = readers.get(tpe)
read.map(_.reads(s)).getOrElse(JsError(s"Unsupported animal: $tpe"))
}
}
}
如果您不想使用此样板文件,可以查看this library(使用无形)。
答案 1 :(得分:1)
您可以尝试制作这样的自定义阅读器:
implicit val animalReads = new Reads[Animal] {
def reads(js: JsValue): Animal = {
(js \ "type").as[String] match {
case "cat" => Cat( (js \ "needsMilk").as[Boolean] )
case "goat" => Goat( (js \ "hooves").as[String] )
case _ => throw new JsonValidationError("Unsupported animal")
}
}
}