从展平的元组

时间:2017-01-16 00:56:17

标签: scala tuples shapeless case-class

背景

我正在使用Play Framework和Slick处理API。为了避免重复的锅炉板,我想在没有ID字段的情况下定义我的公共JSON模型,并将它们包装在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]))
}

感谢ReadsWrites定义的神奇之处,我可以轻松处理带有或不带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将以元组或案例类的形式从数据库中返回行,具体取决于我正在使用的查询。显然,我可以编写很多非常简单的帮助方法来将元组/ case类转换为每个公共模型:

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))
}

...但随着公共模型数量的增长,这需要维护很多样板。

问题

  1. 是否有一种干净的方式来获取Tuple4[Long, String, Option[String], String]case class WikiRow(id: Long, name: String, source: Option[String], text: String)并将其转换为WithId[Wiki](反之亦然)?

  2. 在我介绍其他公共模型case class Template(name: String, description: String)之后,我们是否可以将解决方案从#1推广到现在处理将Tuple3[Long, String, Strong]转换为WithId[Template](反之亦然)?

  3. 如果我们将一个字段放入未在公共模型中使用的私有模型,会发生什么?例如,case class WikiRow(id: Long, name: String, source: Option[String], text: String, hidden: Boolean)hidden字段需要在转到WikiRow => WithId[Wiki]时删除,并在转到WithId[Wiki] => WikiRow时从其他来源提供。

1 个答案:

答案 0 :(得分:3)

关于问题1和问题2:是的,有可能是没有形状的,正如所建议的那样。这是一个从元组或行到WithId[T]的解决方案。

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class WikiRow(id: Long, name: String, source: Option[String], text: String) 
case class Wiki(name: String, source: Option[String], text: String)
case class WithId[T](id: Long, item: T)

def createWithId[T] = new WithIdCreator[T]

class WithIdCreator[Out] {
  import shapeless._
  import shapeless.ops.hlist.IsHCons
  def apply[In, InGen <: HList, Tail <: HList](in: In)(
    implicit
    genIn: Generic.Aux[In,InGen],
    hcons: IsHCons.Aux[InGen,Long,Tail],
    genOut: Generic.Aux[Out,Tail]
  ): WithId[Out] = {
    val rep = genIn.to(in)
    val id = hcons.head(rep)
    val tail = hcons.tail(rep)
    WithId(id, genOut.from(tail))
  }
}

// Exiting paste mode, now interpreting.

defined class WikiRow
defined class Wiki
defined class WithId
createWithId: [T]=> WithIdCreator[T]
defined class WithIdCreator

scala> createWithId[Wiki](WikiRow(3L, "foo", None, "barbaz"))
res1: WithId[Wiki] = WithId(3,Wiki(foo,None,barbaz))

scala> createWithId[Wiki]((3L, "foo", None: Option[String], "barbaz"))
res2: WithId[Wiki] = WithId(3,Wiki(foo,None,barbaz))

scala> case class Template(name: String, description: String)
defined class Template

scala> createWithId[Template]((3L, "foo", "barbaz"))
res3: WithId[Template] = WithId(3,Template(foo,barbaz))

另一方向的转换或多或少类似。

我认为没有理由认为3也不可能,但是你必须再次重写转换以处理丢弃或手动提供参数。

您可以在shapeless guide中详细了解相关信息。所有必要的概念都在那里解释。

相关问题