【问题标题】:Shapeless Json parsing by LabelledGenericLabelledGeneric 解析无形 Json
【发布时间】: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


【解决方案1】:

这不完全是你的情况,但它似乎非常接近你的需要。也许你可以使用这个想法。我添加了一些存根以使代码开箱即用,无需依赖循环。这个想法是迭代 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 中的异常

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-10
    相关资源
    最近更新 更多