代码之家  ›  专栏  ›  技术社区  ›  Travis Brown

使用circe解码json对象时捕获未使用的字段

  •  8
  • Travis Brown  · 技术社区  · 7 年前

    假设我有一个case类,如下所示,我想将一个json对象解码到其中,所有尚未使用的字段都以一个特殊的成员结尾,用于处理剩余的内容:

    import io.circe.Json
    
    case class Foo(a: Int, b: String, leftovers: Json)
    

    在scala和circe中最好的方法是什么?

    (注:我见过这样的问题 a few times ,所以我要为子孙后代问答。)

    1 回复  |  直到 7 年前
        1
  •  13
  •   Travis Brown    7 年前

    有几种方法可以解决这个问题。一个相当简单的方法是在解码后过滤掉您使用过的密钥:

    import io.circe.{ Decoder, Json, JsonObject }
    
    implicit val decodeFoo: Decoder[Foo] =
      Decoder.forProduct2[Int, String, (Int, String)]("a", "b")((_, _)).product(
        Decoder[JsonObject]
      ).map {
        case ((a, b), all) =>
          Foo(a, b, Json.fromJsonObject(all.remove("a").remove("b")))
      }
    

    正如你所料:

    scala> val doc = """{ "something": false, "a": 1, "b": "abc", "0": 0 }"""
    doc: String = { "something": false, "a": 1, "b": "abc", "0": 0 }
    
    scala> io.circe.jawn.decode[Foo](doc)
    res0: Either[io.circe.Error,Foo] =
    Right(Foo(1,abc,{
      "something" : false,
      "0" : 0
    }))
    

    这种方法的缺点是,您必须维护代码,以从使用中删除已单独使用的密钥,这可能是容易出错的。另一种方法是使用circe的state monad驱动解码工具:

    import cats.data.StateT
    import cats.instances.either._
    import io.circe.{ ACursor, Decoder, Json }
    
    implicit val decodeFoo: Decoder[Foo] = Decoder.fromState(
      for {
        a <- Decoder.state.decodeField[Int]("a")
        b <- Decoder.state.decodeField[String]("b")
        rest <- StateT.inspectF((_: ACursor).as[Json])
      } yield Foo(a, b, rest)
    )
    

    它的工作方式与前一个解码器相同(除了解码失败时会出现的一些小错误之外):

    scala> io.circe.jawn.decode[Foo](doc)
    res1: Either[io.circe.Error,Foo] =
    Right(Foo(1,abc,{
      "something" : false,
      "0" : 0
    }))
    

    后一种方法不需要在多个地方更改使用的字段,而且它的优点是看起来更像在circe中手动编写的任何其他解码器。