【发布时间】:2019-05-16 07:43:27
【问题描述】:
我是 Shapeless 的新手,如果我看不到简单的解决方案,我很抱歉。
假设我们有数据案例类
case class Test(x: Int, y: String, z: Double) extends Row
Json(不包含case类的所有字段)
{ "x": 10, "y": "foo" }
以及将 json 解码为的特殊情况类
case class UpdateRequest[T <: Row](updates: List[Update[T]])
Update[Row] 是一个密封的 trait,用于帮助在 slick 中执行可组合的更新,在这里发布非常重要,实际上这与问题无关。
目标是解析 json(我使用 circe)并检查每个 json 字段是否存在于作为类型参数提供给 UpdateRequest 的 case 类中,并尝试使用从 case 类获取的类型解码 json 值。
例如,我需要像这样的工作
parse(json).as[UpdateRequest[Test]] ...
所以我们需要自定义解码器和 shapeless 混合。
这是总体描述,如果您显示完整的解决方案会很棒,但当前的问题是我无法从字段列表中按名称找到特定字段
例如,我可以解码特定字段,例如
def decode[T, U](s: (Symbol with Tagged[U], T), c: HCursor)(implicit decoder: Decoder[T]) = {
c.downField(s._1.name).as[T]
}
val test = Test(1, "foo", 1.5)
val lg = LabelledGeneric[Test]
val fields = Fields[lg.Repr].apply(lg.to(test))
decode(fields.head)
但是如何遍历所有字段并首先按名称查找?
我猜可能是这样的
def decode[...](fields: [...], c: HCursor, fieldName: String)(implicit decoder: Decoder[T]) = {
// try to find field by name, if exists try to decode
...
}
提前感谢您的帮助。
编辑
逐步简化示例。
我们有代表 DB 中行的数据类。
trait Row
case class User(id: Int, age: Int, name: String) extends Row
case class SomeOtherData(id: Int, field1: List[String], field2: Double) extends Row
...
我们的 API 可以接受路由上的任何 json,例如
PUT http://192.168.0.1/users/:userId
PUT http://192.168.0.1/other/:otherId
...
例如我们调用 PUT http://192.168.0.1/users/:userId next json
{ "age": 100 }
我们有特殊的类来解码json到
UpdateRequest[T <: Row](updates: List[Update[T]])
“更新”将在哪里
List(
Update((_: User).age, 100)
)
您可以在https://www.missingfaktor.me/writing/2018/08/12/composable-table-updates-in-slick/找到使用这种方法的完整示例
但再一次,比赛结束时会发生什么并不重要,因为问题的原因是其他的。
因此,我们将传入的 json 解析为 UpdateRequest[User]。 1)我们遍历 Json 中的所有字段并尝试在 LabelledGeneric[User] 中找到每个字段 2)如果找到该字段,那么我们尝试使用 circe 解码找到的字段类型。否则解码失败。
可能是这样(类型和实现都不对,只是举例说明思路)
object UpdateRequest {
import shapeless._
import shapeless.ops.record._
def decode[T, U](s: (Symbol with Tagged[U], T), c: HCursor)(implicit decoder: Decoder[T]) = {
c.downField(s._1.name).as[T]
}
implicit def decoder[R <: Row, HL <: HList]()(implicit gen: LabelledGeneric.Aux[R, HL]): Decoder[UpdateRequest[R]] = new Decoder[UpdateRequest[R]] {
final def apply(c: HCursor): Decoder.Result[UpdateRequest[R]] = {
c.keys match {
case Some(keys) =>
// we got "age" key from json
// for each json key we try to find field in LabelledGeneric's Repr
// (maybe we need Fields here instead)
// so we found "age" in case class User and determine the type is Int
// and then try to decode to Int
val field = ... //found field from Repr
for {
age <- decode(field, c)
} yield ...
// and after make it as UpdateRequest[Row] (not needed to implement, the problem is above)
case None => Left(DecodingFailure("Empty json", Nil))
}
}
}
}
提前谢谢大家。
【问题讨论】:
-
您尝试完成的任务似乎不太适合无形,因为 json 对象中没有形状良好的类型信息。您可以做的是将您的 Json 对象转换为包含所有字段值的
List[Any],然后尝试将其转换为HList,在转换过程中传递有关此列表的类型信息,例如list.toHlist[Int :: String :: Double :: HNil]。当然,你需要先把这个 HList 类型从 case 类中取出来。此转换可能失败或成功取决于您的List[Any]中的实际内容。 -
我不需要知道 Json 对象中的字段类型。我需要在 UpdateRequest[A] 中作为类型参数提供的案例类的字段中找到它。当我们使用 circe 自动派生时,我们在 json 对象中没有类型,但它工作正常吗?目标是解析 json(我使用 circe)并检查每个 json 字段是否存在于作为 UpdateRequest 的类型参数提供的案例类中,并尝试使用从案例类获取的类型解码 json 值。
-
为什么要在 JSON 模型类中公开持久层实现 (Slick)?这似乎是一个设计缺陷——如果你离开 Slick 会怎样?
-
我没有问任何关于设计的事情。如果您想帮助我解决问题,请仔细阅读,否则如果明天不会来怎么办?
-
很难理解你真正想要达到的目标。您是否尝试从案例类中获取字段名称列表,然后检查其中是否有特定字段?在你最后一个
decode方法存根中。你需要一种包含字段名称的 HList 结构并检查这个结构是否包含fieldName在最后一个参数中传递?这段代码对你有帮助吗val labl = LabelledGeneric[Foo]; val names = Keys[labl.Repr].apply.toList.map(_.name)?
标签: json scala shapeless circe