Scala 元组到层次结构

Scala tuples to hierarchy

在 Slick 中,SQL 像这样连接:

select a.id, a.name, b.name, c.name, c.added from A a join B b join C c

将 return 这样的元组:

(1,Jim,Some(Foo),Mark,Some(2018-06-14T08:06:56Z))
(1,Jim,Some(Foo),Anne,Some(2018-06-14T12:04:50Z))
(1,Jim,Some(Bar),Jose,Some(2018-06-14T17:40:19Z))

将此类元组转换为层次结构的最 Scala 方式是什么?例如:

Map(
    "id" -> 1,
    "name" -> "Jim",
    "projects" -> List(
        Map(
            "name" -> "Foo",
            "staffs" -> List(
                Map(
                    "name" -> "Mark",
                    "added" -> "2018-06-14T08:06:56Z"
                ),
                Map(
                    "name" -> "Anne",
                    "added" -> "2018-06-14T12:04:50Z"
                )
            )
        ),
        Map(
            "name" -> "Bar",
            "staffs" -> List(
                Map(
                    "name" -> "Jose",
                    "added" -> "2018-06-14T17:40:19Z"
                )
            )
        )
    )
)

请注意,生成的对象将使用 Jackson 转换为 JSON。

使用 groupBy() 运算符收集具有公共字段的元组。下面的第一个 groupBy 选择前两个字段并将它们映射到 idnameMap 条目。第二个嵌套的 groupBy 在项目字段上进行子选择并创建项目列表。第三次迭代现在选择与项目相关的人员。

val listOfProjectMaps = resultList.groupBy(t => (t._1, t._2))
 .map({
  case (k, v) => Map(
    "id" -> k._1,
    "name" -> k._2,
    "projects" ->
      v.groupBy(tv => tv._3.get)
      .map({
        case (k1, v1) =>
          Map(
            "name" -> k1,
            "staffs" ->
              v1.map(v2 => Map(
                "name" -> v2._4,
                "added" -> v2._5.get)))
      }))
})

请注意这种方法,虽然直截了当并不能很好地处理坏数据。

这是我的做法; 首先为您的项目域创建 classes。

case class Manager(managerId: Int, name: String)

case class Project(projectId: Int, name: String, staffIds: List[Int])

case class Staff(staffId: Int, name: String, added: String) // or java.time.ZonedDateTime

然后像本例一样设置 Slick 数据库映射 gist。您可以使用 split(",")toIntmkString(",")List[Int] 保留在 Project 中,或者使用连接 table 和 case class StaffOnProject(projectId: Int, staffId: Int) . 现在你可以在你的服务中使用连接来收集你的数据 class 就像这样;我会说有很多方法可以做到这一点。

// Probably not what I would do but this is the structure you asked for
case class ProjectWithStaff(project: Project, staff: List[Staff])
case class ManagerWithProjectsAndStaff(manager: Manager, projects: List[ProjectWithStaff])

def getManagerWithProjectsAndStaff(managerId:Int): Future[ManagerWithProjectsAndStaff] =
  for {
    manager <- db.run(Managers.byId(managerId).result.head)
    projects <- db.run(Projects.byManager(managerId).result)
    staffs <- db.run(Staffs.table.filter(_.staffId inSet projects.flatMap(_.staffIds).toSet).result)
  } yield ManagerWithProjectsAndStaff(
    manager,
    projects.map(p => {
      val projectStaff = staffs.filter(s => p.staffIds.contains(s.staffId))
      ProjectWithStaff(p, projectStaff)
    })
  )

所有这些案例 classes 都可以由 Jackson 进行(反)序列化。 ManagerWithProjectsAndStaff 将以您描述的格式序列化。