【发布时间】:2017-01-16 00:56:17
【问题描述】:
背景
我正在开发一个同时使用 Play Framework 和 Slick 的 API。为了避免重复的样板,我想定义我的公共 JSON 模型而不使用其 ID 字段并将它们包装在 WithId 容器中。
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class WithId[T](id: Long, item: T)
case class Wiki(name: String, source: Option[String], text: String)
object WithId {
implicit def withIdRead[T : Reads] : Reads[WithId[T]] = (
(JsPath \ "id").read[Long] and
JsPath.read[T]
)((id, item) => WithId(id, item))
implicit def withIdWrite[T : Writes] : Writes[WithId[T]] = (
(JsPath \ "id").write[Long] and
JsPath.write[T]
).apply(unlift(WithId.unapply[T]))
}
感谢Reads 和Writes 定义的魔力,我可以轻松处理带或不带id 的JSON。
scala> val rawIdJson = """{"id": 123, "name": "My First Wiki", "text": "This is my first wiki article"}"""
rawIdJson: String = {"id": 123, "name": "My First Wiki", "text": "This is my first wiki article"}
scala> val withId = Json.parse(rawIdJson).validate[WithId[Wiki]].get
withId: model.util.WithId[model.entity.Wiki] = WithId(123,Wiki(My First Wiki,None,This is my first wiki article))
scala> val withIdJson = Json.toJson(withId)
withIdJson: play.api.libs.json.JsValue = {"id":123,"name":"My First Wiki","text":"This is my first wiki article"}
scala> val rawJson = """{"name": "My First Wiki", "text": "This is my first wiki article"}"""
rawJson: String = {"name": "My First Wiki", "text": "This is my first wiki article"}
scala> val withoutId = Json.parse(rawJson).validate[Wiki].get
withoutId: model.entity.Wiki = Wiki(My First Wiki,None,This is my first wiki article)
scala> val withoutIdJson = Json.toJson(withoutId)
withoutIdJson: play.api.libs.json.JsValue = {"name":"My First Wiki","text":"This is my first wiki article"}
一切顺利。
我现在遇到的问题是 Slick 将以元组或案例类的形式从数据库返回行,具体取决于我使用的查询。显然,我可以编写很多非常直接的辅助方法来将元组/案例类转换为每个公共模型:
object Wiki {
implicit val wikiFmt = Json.format[Wiki]
def fromRow(row: WikiRow) : WithId[Wiki] = WithId(row.id, Wiki(row.name, row.source, row.text))
def fromRow(tup: (Long, String, Option[String], String)) : WithId[Wiki] = WithId(tup._1, Wiki(tup._2, tup._3, tup._4))
}
...但是随着公共模型数量的增长,需要维护的样板文件很多。
问题
有没有一种干净的方法可以将
Tuple4[Long, String, Option[String], String]或case class WikiRow(id: Long, name: String, source: Option[String], text: String)转换为WithId[Wiki](反之亦然)?-
一旦我介绍了另一个公共模型,如
李>case class Template(name: String, description: String),我们能否将解决方案从 #1 推广到现在处理将Tuple3[Long, String, Strong]转换为WithId[Template](反之亦然)? 如果我们将一个未在公共模型中使用的字段放入私有模型中会发生什么?例如,
case class WikiRow(id: Long, name: String, source: Option[String], text: String, hidden: Boolean)。hidden字段在转到WikiRow => WithId[Wiki]时需要删除,并在转到WithId[Wiki] => WikiRow时从另一个来源提供。
【问题讨论】:
-
如何定义从元组或案例类到
WithId[Model]类的隐式转换? -
@Yaneeve - 这与为每个公共模型定义
fromRow有何不同?我觉得WithId[T]可以获取一个元组,将Long的头部从它身上剪下来,然后将元组的尾部传递给T的构造函数。我只是不确定那是什么魔法。 -
没有什么不同,只是您不必显式编写调用。至于魔法,很遗憾我帮不了你。只是提醒一下,在这种情况下,魔术只是运行我们看不到(或理解)的代码。关于元组的魔术师是shapless。我不太了解它,但它可能会帮助您实现您的愿望
标签: scala tuples shapeless case-class