【问题标题】:How can I deserialize an non-fixed array of jsons using Circe's manual decoder?如何使用 Circe 的手动解码器反序列化非固定的 json 数组?
【发布时间】:2020-08-06 10:11:46
【问题描述】:

我有一个如下所示的 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._,目的是使用 asJson 将来自 get 请求的传入数据直接解析为 Json。

get 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调用都会产生不同数量的用户。

【问题讨论】:

    标签: scala circe sttp


    【解决方案1】:

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

    我在这里引用 Travis:

    最好建立你需要解析的实例 顶级 JSON 对象组合......即有一个 Decoder[User] 仅解码单个用户 JSON 对象,然后使用 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映射到Decoder,我们将Decoder的结果包装到Users案例类中。

    【讨论】:

      【解决方案2】:

      如果您想解码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]]]
      

      【讨论】:

        猜你喜欢
        • 2021-11-07
        • 2017-07-06
        • 2019-10-30
        • 1970-01-01
        • 2017-06-02
        • 1970-01-01
        • 1970-01-01
        • 2015-11-22
        • 2013-02-04
        相关资源
        最近更新 更多