首页 > 解决方案 > 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))
      }
    }
  }
}

谢谢大家。

标签: jsonscalashapelesscirce

解决方案


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

您可能只需要替换OptionDecoder.Result处理异常toEncodedElements


推荐阅读