2

我有一个看起来像这样的 JSON:

{
  "data": [
    {
      "id": "1",
      "email": "hello@world.com",
      "name": "Mr foo",
      "roles": [
        "Chief Bar Officer"
      ],
      "avatar_url": null,
      "phone_number": null
    },
    {
      "id": "2",
      "email": "bye@world.com",
      "name": "Mr baz",
      "roles": [
        "Chief Baz Officer"
      ],
      "avatar_url": null,
      "phone_number": null
    }
  ]
}

我主要对解析/反序列化数据列表感兴趣,我想手动进行(出于某种神秘原因,我更喜欢手动方式)。

如果这是相关的,我正在使用 sttp 的 circe 库sttp.client.circe._,目的是将来自 get 请求的传入数据直接解析为使用asJson.

获取 sttp 请求类似于:

val r1 = basicRequest
    .get(uri"https://woooo.woo.wo/v1/users")
    .header("accept", "application/json")
    .header("Authorization", "topsecret"
    .response(asJson[SomeClass])

这是我到目前为止所尝试的:

// Define the case class
case class User(
    id: String,
    email: String,
    name: String,
    roles: List[String],
    avatar_url: Option[String],
    phone_number: Option[String]
)

// Define the manual deserializer

case object User {

  implicit val userDecoder: Decoder[User] = (hCursor: HCursor) => {
    val data = hCursor.downField("data").downArray
    for {
      id <- data.get[String]("id")
      email <- data.get[String]("email")
      name <- data.get[String]("name")
      roles <- data.get[List[String]]("roles")
      avatarUrl <- data.get[Option[String]]("avatarUrl")
      phoneNumber <- data.get[Option[String]]("phoneNumber")
    } yield User(id, email, name, roles, avatarUrl, phoneNumber)
  }
}

我的方法的问题(我认为)是.downArray让我只序列化用户数组中的第一个用户。

我的目标是能够拥有一些用户序列(List[User]可能是这样),但目前我最终只反序列化数组中的一个用户。

值得一提的是,“data”数组,并不包含固定数量的用户,每次api调用都会产生不同数量的用户。

4

2 回答 2

3

感谢Travis Browncirce Gitter 社区帮助我解决这个问题。

我在这里引用特拉维斯:

最好构建您需要组合解析顶级 JSON 对象的实例……即有一个仅解码单个用户 JSON 对象的 Decoder[User],然后使用 Decoder[List[User]].at( "data") 或类似的东西来解码包含带有 JSON 数组的数据字段的顶级 JSON 对象。

我最终得到了一个看起来像这样的实现:

case class Users(users: List[User])

case object User {

  implicit val usrDecoder: Decoder[User] = (hCursor: HCursor) => {

    for {
      id <- hCursor.get[String]("id")
      email <- hCursor.get[String]("email")
      name <- hCursor.get[String]("name")
      roles <- hCursor.get[List[String]]("roles")
      avatarUrl <- hCursor.get[Option[String]]("avatarUrl")
      phoneNumber <- hCursor.get[Option[String]]("phoneNumber")
    } yield User(id, email, name, roles, avatarUrl, phoneNumber)
  }

  implicit val decodeUsers: Decoder[Users] =
    Decoder[List[User]].at("data").map(Users)

}

这个想法是分别组合用户的解码器和用户集合的解码器。然后通过映射Users到解码器,我们将解码器的结果包装到用户案例类中。

于 2020-08-06T12:15:23.400 回答
1

如果要解码,则List[User]需要为确切类型创建解码器List[User]。它可能看起来像:

implicit val userDecoder: Decoder[List[User]] = (hCursor: HCursor) => {
    Either.fromOption(
      hCursor.downField("data").values,
      DecodingFailure("Can't decode data", Nil)
    ).flatMap { values =>
      values.toList.map(_.hcursor).traverse {
        userCursor =>
          for {
            id <- userCursor.get[String]("id")
            email <- userCursor.get[String]("email")
            name <- userCursor.get[String]("name")
            roles <- userCursor.get[List[String]]("roles")
            avatarUrl <- userCursor.get[Option[String]]("avatarUrl")
            phoneNumber <- userCursor.get[Option[String]]("phoneNumber")
          } yield User(id, email, name, roles, avatarUrl, phoneNumber)
      }
    }
  }

然后你可以像这样使用它:

json.as[List[User]]

这可能不是最好的主意,因为 circe 已经可以将 json 数组解码为列表,所以最好为用户创建解码器(解码对象):

implicit val userDecoder: Decoder[User] = (hCursor: HCursor) =>
  for {
    id <- hCursor.get[String]("id")
    email <- hCursor.get[String]("email")
    name <- hCursor.get[String]("name")
    roles <- hCursor.get[List[String]]("roles")
    avatarUrl <- hCursor.get[Option[String]]("avatarUrl")
    phoneNumber <- hCursor.get[Option[String]]("phoneNumber")
} yield User(id, email, name, roles, avatarUrl, phoneNumber)

在这种情况下,您必须手动向下data字段:

json.hcursor.downField("data").as[List[User]]

另一种可能性是只为data包装器创建案例类:

  case class Data[D](data: D)

  object Data {
    implicit def dataDecoder[D: Decoder]: Decoder[Data[D]] =
      (hCursor: HCursor) => hCursor.get[D]("data").map(Data(_))
  }

然后您可以执行以下操作:

json.as[Data[List[User]]]
于 2020-08-06T12:15:50.097 回答