json - LabelledGeneric 解析无形 Json
问题描述
我是 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] 是一个密封的特性,可以帮助在 slick 中执行可组合的更新,在这里发布非常重要,实际上这与问题无关。
目标是解析 json(我使用 circe)并检查每个 json 字段是否存在于作为类型参数提供给 UpdateRequest 的 case 类中,并尝试使用从 case 类获取的类型解码 json 值。
例如,我需要像这样工作
parse(json).as[UpdateRequest[Test]] ...
所以我们需要自定义解码器与无形混合。
这是总体描述,如果您显示完整的解决方案会很棒,但当前的问题是我无法从字段列表中按名称找到特定字段
例如,我可以解码特定字段,例如
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
...
}
提前感谢您的帮助。
编辑
逐步简化的示例。
我们有代表数据库中行的数据类。
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))
}
}
}
}
谢谢大家。
解决方案
这不完全是您的情况,但它似乎非常接近您的需要。也许你可以使用这个想法。我添加了一些存根以使代码开箱即用,无需依赖循环。这个想法是迭代 case case 字段而不是 json 中的字段。不过,这个设计看起来很奇怪,因为我必须创建一个案例类的实例,只是为了从中提取字段名称和类型,而不使用那里的值。
trait Decoder[T] {
def apply(t:String): T
}
implicit val decoderStr: Decoder[String] = (t) => t
implicit val decoderInt: Decoder[Int] = (t) => t.toInt
implicit val decoderFloat: Decoder[Double] = (t) => t.toDouble
val c = new {
def downField(name:String):String = name match {
case "x" => "5"
case "y" => "some string"
case "z" => "2.0"
}
}
implicit class Stbb (a: String) {
def as[T](implicit dc: Decoder[T])= dc.apply(a)
}
//end of stubs
import shapeless._
import shapeless.labelled._
case class Test(x: Int, y: String, z: Double)
val test = Test(1, "foo", 1.5)
val lg = LabelledGeneric[Test]
val genericTest = lg.to(test)
object mapToEncoded extends Poly1 {
implicit def toEncodedElements[K,A](implicit key: Witness.Aux[K], dec: Decoder[A]) =
at[FieldType[K,A]](_ => Some(c.downField(key.value.toString.drop(1)).as[A])))
}
//HList with Option[T] values decoded to proper type
val res = genericTest.map(mapToEncoded)
println(res)
您可能只需要替换Option
并Decoder.Result
处理异常toEncodedElements
推荐阅读
- python - Pandas - 如果 Column 包含多个条件,则 Python 删除行
- python - 如何使用预先指定的颜色调色板找到图像的主色?
- node.js - `firebase deploy`ing Google Smart Home codelab 操作时出错
- php - PHP - 转换 unicode 符号
- mysql - 如何在mysql中按月统计不同范围和分组的数据?
- python - python 3.x上的列表索引超出范围错误
- python - 如何创建多个迭代
- mysql - 如何使用 Prestashop 的后台 sql 管理器从两个不同的表中导出用户数据?
- css - 动画文本持续时间
- javascript - 外部 JSON 错误中的意外令牌